1 /* GStreamer openaptx audio decoder
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-openaptxdec
24 * @title: openaptxdec
25 *
26 * This element decodes Bluetooth aptX or aptX-HD stream to raw S24LE integer stereo PCM audio.
27 * Accepts audio/aptx or audio/aptx-hd input streams.
28 *
29 * ## Example pipelines
30 * |[
31 * gst-launch-1.0 -v audiotestsrc ! avenc_aptx ! openaptxdec ! audioconvert ! autoaudiosink
32 * ]| Decode a sine wave encoded with AV encoder and listen to result.
33 * |[
34 * gst-launch-1.0 -v audiotestsrc ! avenc_aptx ! openaptxdec autosync=0 ! audioconvert ! autoaudiosink
35 * ]| Decode a sine wave encoded with AV encoder and listen to result, stream autosync disabled.
36 *
37 */
38
39 #ifdef HAVE_CONFIG_H
40 #include <config.h>
41 #endif
42
43 #include "gstopenaptxdec.h"
44 #include "openaptx-plugin.h"
45
46 enum
47 {
48 PROP_0,
49 PROP_AUTOSYNC
50 };
51
52 GST_DEBUG_CATEGORY_STATIC (openaptx_dec_debug);
53 #define GST_CAT_DEFAULT openaptx_dec_debug
54
55 #define parent_class gst_openaptx_dec_parent_class
56 G_DEFINE_TYPE (GstOpenaptxDec, gst_openaptx_dec, GST_TYPE_AUDIO_DECODER);
57 GST_ELEMENT_REGISTER_DEFINE (openaptxdec, "openaptxdec", GST_RANK_NONE,
58 GST_TYPE_OPENAPTX_DEC);
59
60 static GstStaticPadTemplate openaptx_dec_sink_factory =
61 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, 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 static GstStaticPadTemplate openaptx_dec_src_factory =
66 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
67 GST_STATIC_CAPS ("audio/x-raw, format = S24LE,"
68 " rate = [ 1, MAX ], channels = 2, layout = interleaved"));
69
70 static GstFlowReturn
gst_openaptx_dec_handle_frame(GstAudioDecoder * audio_dec,GstBuffer * buffer)71 gst_openaptx_dec_handle_frame (GstAudioDecoder * audio_dec, GstBuffer * buffer)
72 {
73 GstOpenaptxDec *dec = GST_OPENAPTX_DEC (audio_dec);
74 GstMapInfo out_map;
75 GstBuffer *outbuf = NULL;
76 GstFlowReturn ret;
77 guint num_frames;
78 gsize frame_len, output_size;
79 gssize input_size, processed = 0;
80 gsize written = 0;
81 gint synced;
82 gsize dropped;
83
84 /* no fancy draining */
85 if (G_UNLIKELY (!buffer))
86 input_size = 0;
87 else
88 input_size = gst_buffer_get_size (buffer);
89
90 frame_len = aptx_frame_size (dec->hd);
91
92 if (!dec->autosync) {
93 /* we assume all frames are of the same size, this is implied by the
94 * input caps applying to the whole input buffer, and the parser should
95 * also have made sure of that */
96 if (G_UNLIKELY (input_size % frame_len != 0))
97 goto mixed_frames;
98 }
99
100 num_frames = input_size / frame_len;
101
102 /* need one extra frame if autosync is enabled */
103 if (dec->autosync)
104 ++num_frames;
105
106 output_size = num_frames * APTX_SAMPLES_PER_FRAME * APTX_SAMPLE_SIZE;
107
108 outbuf = gst_audio_decoder_allocate_output_buffer (audio_dec, output_size);
109
110 if (outbuf == NULL)
111 goto no_output_buffer;
112
113 if (!gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE)) {
114 gst_buffer_replace (&outbuf, NULL);
115 goto no_output_buffer_map;
116 }
117
118 if (G_LIKELY (buffer)) {
119 GstMapInfo in_map;
120
121 if (!gst_buffer_map (buffer, &in_map, GST_MAP_READ)) {
122 gst_buffer_unmap (outbuf, &out_map);
123 gst_buffer_replace (&outbuf, NULL);
124 goto map_failed;
125 }
126
127 if (dec->autosync) {
128 processed = aptx_decode_sync (dec->aptx_c, in_map.data, in_map.size,
129 out_map.data, output_size, &written, &synced, &dropped);
130 } else {
131 processed = aptx_decode (dec->aptx_c, in_map.data, in_map.size,
132 out_map.data, out_map.size, &written);
133 }
134
135 gst_buffer_unmap (buffer, &in_map);
136 } else {
137 if (dec->autosync) {
138 dropped = aptx_decode_sync_finish (dec->aptx_c);
139 synced = 1;
140 }
141 }
142
143 if (dec->autosync) {
144 if (!synced) {
145 GST_WARNING_OBJECT (dec, "%s stream is not synchronized",
146 aptx_name (dec->hd));
147 }
148 if (dropped) {
149 GST_WARNING_OBJECT (dec,
150 "%s decoder dropped %" G_GSIZE_FORMAT " bytes from stream",
151 aptx_name (dec->hd), dropped);
152 }
153 }
154
155 if (processed != input_size) {
156 GST_WARNING_OBJECT (dec,
157 "%s decoding error, processed = %" G_GSSIZE_FORMAT ", "
158 "written = %" G_GSSIZE_FORMAT ", input size = %" G_GSIZE_FORMAT,
159 aptx_name (dec->hd), processed, written, input_size);
160 }
161
162 gst_buffer_unmap (outbuf, &out_map);
163
164 GST_LOG_OBJECT (dec, "%s written = %" G_GSSIZE_FORMAT,
165 aptx_name (dec->hd), written);
166
167 done:
168 if (G_LIKELY (outbuf)) {
169 if (G_LIKELY (written > 0))
170 gst_buffer_set_size (outbuf, written);
171 else
172 gst_buffer_replace (&outbuf, NULL);
173 }
174
175 ret = gst_audio_decoder_finish_frame (audio_dec, outbuf, 1);
176
177 if (G_UNLIKELY (!buffer))
178 ret = GST_FLOW_EOS;
179
180 return ret;
181
182 /* ERRORS */
183 mixed_frames:
184 {
185 GST_WARNING_OBJECT (dec, "inconsistent input data/frames, skipping");
186 goto done;
187 }
188 no_output_buffer_map:
189 {
190 GST_ELEMENT_ERROR (dec, RESOURCE, FAILED,
191 ("Could not map output buffer"),
192 ("Failed to map allocated output buffer for write access."));
193 return GST_FLOW_ERROR;
194 }
195 no_output_buffer:
196 {
197 GST_ELEMENT_ERROR (dec, RESOURCE, FAILED,
198 ("Could not allocate output buffer"),
199 ("Audio decoder failed to allocate output buffer to hold an audio frame."));
200 return GST_FLOW_ERROR;
201 }
202 map_failed:
203 {
204 GST_ELEMENT_ERROR (dec, RESOURCE, FAILED,
205 ("Could not map input buffer"),
206 ("Failed to map incoming buffer for read access."));
207 return GST_FLOW_ERROR;
208 }
209 }
210
211 static gboolean
gst_openaptx_dec_set_format(GstAudioDecoder * audio_dec,GstCaps * caps)212 gst_openaptx_dec_set_format (GstAudioDecoder * audio_dec, GstCaps * caps)
213 {
214 GstOpenaptxDec *dec = GST_OPENAPTX_DEC (audio_dec);
215 GstAudioInfo info;
216 GstStructure *s;
217 gint rate;
218
219 s = gst_caps_get_structure (caps, 0);
220 gst_structure_get_int (s, "rate", &rate);
221
222 /* let's see what is in the output caps */
223 dec->hd = gst_structure_has_name (s, "audio/aptx-hd");
224
225 /* reinitialize codec */
226 if (dec->aptx_c)
227 aptx_finish (dec->aptx_c);
228
229 GST_INFO_OBJECT (dec, "Initialize %s codec", aptx_name (dec->hd));
230 dec->aptx_c = aptx_init (dec->hd);
231
232 /* set up output format */
233 gst_audio_info_init (&info);
234 gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S24LE,
235 rate, APTX_NUM_CHANNELS, NULL);
236 gst_audio_decoder_set_output_format (audio_dec, &info);
237
238 return TRUE;
239 }
240
241 static void
gst_openaptx_dec_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)242 gst_openaptx_dec_set_property (GObject * object, guint prop_id,
243 const GValue * value, GParamSpec * pspec)
244 {
245 GstOpenaptxDec *dec = GST_OPENAPTX_DEC (object);
246
247 switch (prop_id) {
248 case PROP_AUTOSYNC:
249 dec->autosync = g_value_get_boolean (value);
250 break;
251 default:
252 break;
253 }
254 }
255
256 static void
gst_openaptx_dec_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)257 gst_openaptx_dec_get_property (GObject * object, guint prop_id, GValue * value,
258 GParamSpec * pspec)
259 {
260 GstOpenaptxDec *dec = GST_OPENAPTX_DEC (object);
261
262 switch (prop_id) {
263 case PROP_AUTOSYNC:{
264 g_value_set_boolean (value, dec->autosync);
265 break;
266 }
267 default:{
268 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269 break;
270 }
271 }
272 }
273
274 static gboolean
gst_openaptx_dec_start(GstAudioDecoder * audio_dec)275 gst_openaptx_dec_start (GstAudioDecoder * audio_dec)
276 {
277 return TRUE;
278 }
279
280 static gboolean
gst_openaptx_dec_stop(GstAudioDecoder * audio_dec)281 gst_openaptx_dec_stop (GstAudioDecoder * audio_dec)
282 {
283 GstOpenaptxDec *dec = GST_OPENAPTX_DEC (audio_dec);
284
285 GST_INFO_OBJECT (dec, "Finish openaptx codec");
286
287 if (dec->aptx_c) {
288 aptx_finish (dec->aptx_c);
289 dec->aptx_c = NULL;
290 }
291
292 return TRUE;
293 }
294
295 static void
gst_openaptx_dec_class_init(GstOpenaptxDecClass * klass)296 gst_openaptx_dec_class_init (GstOpenaptxDecClass * klass)
297 {
298 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
299 GstAudioDecoderClass *base_class = GST_AUDIO_DECODER_CLASS (klass);
300 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
301
302 gobject_class->set_property =
303 GST_DEBUG_FUNCPTR (gst_openaptx_dec_set_property);
304 gobject_class->get_property =
305 GST_DEBUG_FUNCPTR (gst_openaptx_dec_get_property);
306
307 g_object_class_install_property (gobject_class, PROP_AUTOSYNC,
308 g_param_spec_boolean ("autosync", "Auto sync",
309 "Gracefully handle partially corrupted stream in which some bytes are missing",
310 APTX_AUTOSYNC_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
311
312 base_class->start = GST_DEBUG_FUNCPTR (gst_openaptx_dec_start);
313 base_class->stop = GST_DEBUG_FUNCPTR (gst_openaptx_dec_stop);
314 base_class->set_format = GST_DEBUG_FUNCPTR (gst_openaptx_dec_set_format);
315 base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_openaptx_dec_handle_frame);
316
317 gst_element_class_add_static_pad_template (element_class,
318 &openaptx_dec_sink_factory);
319 gst_element_class_add_static_pad_template (element_class,
320 &openaptx_dec_src_factory);
321
322 gst_element_class_set_static_metadata (element_class,
323 "Bluetooth aptX/aptX-HD audio decoder using libopenaptx",
324 "Codec/Decoder/Audio",
325 "Decode an aptX or aptX-HD audio stream using libopenaptx",
326 "Igor V. Kovalenko <igor.v.kovalenko@gmail.com>, "
327 "Thomas Weißschuh <thomas@t-8ch.de>");
328
329 GST_DEBUG_CATEGORY_INIT (openaptx_dec_debug, "openaptxdec", 0,
330 "openaptx decoding element");
331 }
332
333 static void
gst_openaptx_dec_init(GstOpenaptxDec * dec)334 gst_openaptx_dec_init (GstOpenaptxDec * dec)
335 {
336 gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (dec), TRUE);
337 gst_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST
338 (dec), TRUE);
339 GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (dec));
340
341 dec->aptx_c = NULL;
342
343 dec->autosync = APTX_AUTOSYNC_DEFAULT;
344 }
345