1 /* GStreamer RTP LDAC payloader
2 * Copyright (C) 2020 Asymptotic <sanchayan@asymptotic.io>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-rtpldacpay
22 * @title: rtpldacpay
23 *
24 * Payload LDAC encoded audio into RTP packets.
25 *
26 * LDAC does not have a public specification and concerns itself only with
27 * bluetooth transmission. Due to the unavailability of a specification, we
28 * consider the encoding-name as X-GST-LDAC.
29 *
30 * The best reference is [libldac](https://android.googlesource.com/platform/external/libldac/)
31 * and the A2DP LDAC implementation in Android's bluetooth stack [Flouride]
32 * (https://android.googlesource.com/platform/system/bt/+/refs/heads/master/stack/a2dp/a2dp_vendor_ldac_encoder.cc).
33 *
34 * ## Example pipeline
35 * |[
36 * gst-launch-1.0 -v audiotestsrc ! ldacenc ! rtpldacpay mtu=679 ! avdtpsink
37 * ]| This example pipeline will payload LDAC encoded audio.
38 *
39 * Since: 1.20
40 */
41
42 #ifdef HAVE_CONFIG_H
43 #include <config.h>
44 #endif
45
46 #include <gst/audio/audio.h>
47 #include "gstrtpelements.h"
48 #include "gstrtpldacpay.h"
49 #include "gstrtputils.h"
50
51 #define GST_RTP_LDAC_PAYLOAD_HEADER_SIZE 1
52 /* MTU size required for LDAC A2DP streaming */
53 #define GST_LDAC_MTU_REQUIRED 679
54
55 GST_DEBUG_CATEGORY_STATIC (gst_rtp_ldac_pay_debug);
56 #define GST_CAT_DEFAULT gst_rtp_ldac_pay_debug
57
58 #define parent_class gst_rtp_ldac_pay_parent_class
59 G_DEFINE_TYPE (GstRtpLdacPay, gst_rtp_ldac_pay, GST_TYPE_RTP_BASE_PAYLOAD);
60 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpldacpay, "rtpldacpay", GST_RANK_NONE,
61 GST_TYPE_RTP_LDAC_PAY, rtp_element_init (plugin));
62
63 static GstStaticPadTemplate gst_rtp_ldac_pay_sink_factory =
64 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
65 GST_STATIC_CAPS ("audio/x-ldac, "
66 "channels = (int) [ 1, 2 ], "
67 "eqmid = (int) { 0, 1, 2 }, "
68 "rate = (int) { 44100, 48000, 88200, 96000 }")
69 );
70
71 static GstStaticPadTemplate gst_rtp_ldac_pay_src_factory =
72 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
73 GST_STATIC_CAPS ("application/x-rtp, "
74 "media = (string) audio,"
75 "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
76 "clock-rate = (int) { 44100, 48000, 88200, 96000 },"
77 "encoding-name = (string) \"X-GST-LDAC\"")
78 );
79
80 static gboolean gst_rtp_ldac_pay_set_caps (GstRTPBasePayload * payload,
81 GstCaps * caps);
82 static GstFlowReturn gst_rtp_ldac_pay_handle_buffer (GstRTPBasePayload *
83 payload, GstBuffer * buffer);
84
85 /**
86 * gst_rtp_ldac_pay_get_num_frames
87 * @eqmid: Encode Quality Mode Index
88 * @channels: Number of channels
89 *
90 * Returns: Number of LDAC frames per packet.
91 */
92 static guint8
gst_rtp_ldac_pay_get_num_frames(gint eqmid,gint channels)93 gst_rtp_ldac_pay_get_num_frames (gint eqmid, gint channels)
94 {
95 g_assert (channels == 1 || channels == 2);
96
97 switch (eqmid) {
98 /* Encode setting for High Quality */
99 case 0:
100 return 4 / channels;
101 /* Encode setting for Standard Quality */
102 case 1:
103 return 6 / channels;
104 /* Encode setting for Mobile use Quality */
105 case 2:
106 return 12 / channels;
107 default:
108 break;
109 }
110
111 g_assert_not_reached ();
112
113 /* If assertion gets compiled out */
114 return 6 / channels;
115 }
116
117 static void
gst_rtp_ldac_pay_class_init(GstRtpLdacPayClass * klass)118 gst_rtp_ldac_pay_class_init (GstRtpLdacPayClass * klass)
119 {
120 GstRTPBasePayloadClass *payload_class = GST_RTP_BASE_PAYLOAD_CLASS (klass);
121 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
122
123 payload_class->set_caps = GST_DEBUG_FUNCPTR (gst_rtp_ldac_pay_set_caps);
124 payload_class->handle_buffer =
125 GST_DEBUG_FUNCPTR (gst_rtp_ldac_pay_handle_buffer);
126
127 gst_element_class_add_static_pad_template (element_class,
128 &gst_rtp_ldac_pay_sink_factory);
129 gst_element_class_add_static_pad_template (element_class,
130 &gst_rtp_ldac_pay_src_factory);
131
132 gst_element_class_set_static_metadata (element_class, "RTP packet payloader",
133 "Codec/Payloader/Network", "Payload LDAC audio as RTP packets",
134 "Sanchayan Maity <sanchayan@asymptotic.io>");
135
136 GST_DEBUG_CATEGORY_INIT (gst_rtp_ldac_pay_debug, "rtpldacpay", 0,
137 "RTP LDAC payloader");
138 }
139
140 static void
gst_rtp_ldac_pay_init(GstRtpLdacPay * self)141 gst_rtp_ldac_pay_init (GstRtpLdacPay * self)
142 {
143
144 }
145
146 static gboolean
gst_rtp_ldac_pay_set_caps(GstRTPBasePayload * payload,GstCaps * caps)147 gst_rtp_ldac_pay_set_caps (GstRTPBasePayload * payload, GstCaps * caps)
148 {
149 GstRtpLdacPay *ldacpay = GST_RTP_LDAC_PAY (payload);
150 GstStructure *structure;
151 gint channels, eqmid, rate;
152
153 if (GST_RTP_BASE_PAYLOAD_MTU (ldacpay) < GST_LDAC_MTU_REQUIRED) {
154 GST_ERROR_OBJECT (ldacpay, "Invalid MTU %d, should be >= %d",
155 GST_RTP_BASE_PAYLOAD_MTU (ldacpay), GST_LDAC_MTU_REQUIRED);
156 return FALSE;
157 }
158
159 structure = gst_caps_get_structure (caps, 0);
160 if (!gst_structure_get_int (structure, "rate", &rate)) {
161 GST_ERROR_OBJECT (ldacpay, "Failed to get audio rate from caps");
162 return FALSE;
163 }
164
165 if (!gst_structure_get_int (structure, "channels", &channels)) {
166 GST_ERROR_OBJECT (ldacpay, "Failed to get audio rate from caps");
167 return FALSE;
168 }
169
170 if (!gst_structure_get_int (structure, "eqmid", &eqmid)) {
171 GST_ERROR_OBJECT (ldacpay, "Failed to get eqmid from caps");
172 return FALSE;
173 }
174
175 ldacpay->frame_count = gst_rtp_ldac_pay_get_num_frames (eqmid, channels);
176
177 gst_rtp_base_payload_set_options (payload, "audio", TRUE, "X-GST-LDAC", rate);
178
179 return gst_rtp_base_payload_set_outcaps (payload, NULL);
180 }
181
182 /*
183 * LDAC encoder does not handle split frames. Currently, the encoder will
184 * always emit 660 bytes worth of payload encapsulating multiple LDAC frames.
185 * This is as per eqmid and GST_LDAC_MTU_REQUIRED passed for configuring the
186 * encoder upstream. Since the encoder always emit full frames and we do not
187 * need to handle frame splitting, we do not use an adapter and also push out
188 * the buffer as it is received.
189 */
190 static GstFlowReturn
gst_rtp_ldac_pay_handle_buffer(GstRTPBasePayload * payload,GstBuffer * buffer)191 gst_rtp_ldac_pay_handle_buffer (GstRTPBasePayload * payload, GstBuffer * buffer)
192 {
193 GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
194 GstRtpLdacPay *ldacpay = GST_RTP_LDAC_PAY (payload);
195 GstBuffer *outbuf;
196 GstClockTime outbuf_frame_duration, outbuf_pts;
197 guint8 *payload_data;
198 gsize buf_sz;
199
200 outbuf =
201 gst_rtp_base_payload_allocate_output_buffer (GST_RTP_BASE_PAYLOAD
202 (ldacpay), GST_RTP_LDAC_PAYLOAD_HEADER_SIZE, 0, 0);
203
204 /* Get payload */
205 gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
206
207 /* Write header and copy data into payload */
208 payload_data = gst_rtp_buffer_get_payload (&rtp);
209 /* Upper 3 fragment bits not used, ref A2DP v13, 4.3.4 */
210 payload_data[0] = ldacpay->frame_count & 0x0f;
211
212 gst_rtp_buffer_unmap (&rtp);
213
214 outbuf_pts = GST_BUFFER_PTS (buffer);
215 outbuf_frame_duration = GST_BUFFER_DURATION (buffer);
216 buf_sz = gst_buffer_get_size (buffer);
217
218 gst_rtp_copy_audio_meta (ldacpay, outbuf, buffer);
219 outbuf = gst_buffer_append (outbuf, buffer);
220
221 GST_BUFFER_PTS (outbuf) = outbuf_pts;
222 GST_BUFFER_DURATION (outbuf) = outbuf_frame_duration;
223 GST_DEBUG_OBJECT (ldacpay,
224 "Pushing %" G_GSIZE_FORMAT " bytes: %" GST_TIME_FORMAT, buf_sz,
225 GST_TIME_ARGS (GST_BUFFER_PTS (outbuf)));
226
227 return gst_rtp_base_payload_push (GST_RTP_BASE_PAYLOAD (ldacpay), outbuf);
228 }
229