• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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