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