1/*** 2 This file is part of PulseAudio. 3 4 Copyright (C) 2020 Asymptotic <sanchayan@asymptotic.io> 5 6 PulseAudio is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as 8 published by the Free Software Foundation; either version 2.1 of the 9 License, or (at your option) any later version. 10 11 PulseAudio is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <arpa/inet.h> 25#include <stdint.h> 26 27#include <pulsecore/log.h> 28#include <pulsecore/macro.h> 29#include <pulsecore/once.h> 30#include <pulsecore/core-util.h> 31#include <pulse/sample.h> 32#include <pulse/timeval.h> 33#include <pulse/util.h> 34 35#include "a2dp-codecs.h" 36#include "a2dp-codec-api.h" 37#include "a2dp-codec-gst.h" 38 39/* Called from the GStreamer streaming thread */ 40static void app_sink_eos(GstAppSink *appsink, gpointer userdata) { 41 pa_log_debug("Sink got EOS"); 42} 43 44static void gst_deinit_common(struct gst_info *info) { 45 if (!info) 46 return; 47 if (info->app_sink) 48 gst_object_unref(info->app_sink); 49 if (info->bin) 50 gst_object_unref(info->bin); 51} 52 53bool gst_init_common(struct gst_info *info) { 54 GstElement *bin = NULL; 55 GstElement *appsink = NULL; 56 GstAppSinkCallbacks callbacks = { 0, }; 57 58 appsink = gst_element_factory_make("appsink", "app_sink"); 59 if (!appsink) { 60 pa_log_error("Could not create appsink element"); 61 goto fail; 62 } 63 g_object_set(appsink, "sync", FALSE, "async", FALSE, "enable-last-sample", FALSE, NULL); 64 65 callbacks.eos = app_sink_eos; 66 gst_app_sink_set_callbacks(GST_APP_SINK(appsink), &callbacks, info, NULL); 67 68 bin = gst_bin_new(NULL); 69 pa_assert(bin); 70 71 info->app_sink = appsink; 72 info->bin = bin; 73 74 return true; 75 76fail: 77 if (appsink) 78 gst_object_unref(appsink); 79 80 return false; 81} 82 83static GstCaps *gst_create_caps_from_sample_spec(const pa_sample_spec *ss) { 84 gchar *sample_format; 85 GstCaps *caps; 86 uint64_t channel_mask; 87 88 switch (ss->format) { 89 case PA_SAMPLE_S16LE: 90 sample_format = "S16LE"; 91 break; 92 case PA_SAMPLE_S24LE: 93 sample_format = "S24LE"; 94 break; 95 case PA_SAMPLE_S32LE: 96 sample_format = "S32LE"; 97 break; 98 case PA_SAMPLE_FLOAT32LE: 99 sample_format = "F32LE"; 100 break; 101 default: 102 pa_assert_not_reached(); 103 break; 104 } 105 106 switch (ss->channels) { 107 case 1: 108 channel_mask = 0x1; 109 break; 110 case 2: 111 channel_mask = 0x3; 112 break; 113 default: 114 pa_assert_not_reached(); 115 break; 116 } 117 118 caps = gst_caps_new_simple("audio/x-raw", 119 "format", G_TYPE_STRING, sample_format, 120 "rate", G_TYPE_INT, (int) ss->rate, 121 "channels", G_TYPE_INT, (int) ss->channels, 122 "channel-mask", GST_TYPE_BITMASK, channel_mask, 123 "layout", G_TYPE_STRING, "interleaved", 124 NULL); 125 126 pa_assert(caps); 127 return caps; 128} 129 130bool gst_codec_init(struct gst_info *info, bool for_encoding, GstElement *transcoder) { 131 GstPad *pad; 132 GstCaps *caps; 133 GstEvent *event; 134 GstSegment segment; 135 GstEvent *stream_start; 136 guint group_id; 137 138 pa_assert(transcoder); 139 140 info->seq_num = 0; 141 142 if (!gst_init_common(info)) 143 goto common_fail; 144 145 gst_bin_add_many(GST_BIN(info->bin), transcoder, info->app_sink, NULL); 146 147 if (!gst_element_link_many(transcoder, info->app_sink, NULL)) { 148 pa_log_error("Failed to link codec elements into pipeline"); 149 goto pipeline_fail; 150 } 151 152 pad = gst_element_get_static_pad(transcoder, "sink"); 153 pa_assert_se(gst_element_add_pad(info->bin, gst_ghost_pad_new("sink", pad))); 154 /** 155 * Only the sink pad is needed to push buffers. Cache it since 156 * gst_element_get_static_pad is relatively expensive and verbose 157 * on higher log levels. 158 */ 159 info->pad_sink = pad; 160 161 if (gst_element_set_state(info->bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { 162 pa_log_error("Could not start pipeline"); 163 goto pipeline_fail; 164 } 165 166 /* First, send stream-start sticky event */ 167 group_id = gst_util_group_id_next(); 168 stream_start = gst_event_new_stream_start("gst-codec-pa"); 169 gst_event_set_group_id(stream_start, group_id); 170 gst_pad_send_event(info->pad_sink, stream_start); 171 172 /* Retrieve the pad that handles the PCM format between PA and GStreamer */ 173 if (for_encoding) 174 pad = gst_element_get_static_pad(transcoder, "sink"); 175 else 176 pad = gst_element_get_static_pad(transcoder, "src"); 177 178 /* Second, send caps sticky event */ 179 caps = gst_create_caps_from_sample_spec(info->ss); 180 pa_assert_se(gst_pad_set_caps(pad, caps)); 181 gst_caps_unref(caps); 182 gst_object_unref(GST_OBJECT(pad)); 183 184 /* Third, send segment sticky event */ 185 gst_segment_init(&segment, GST_FORMAT_TIME); 186 event = gst_event_new_segment(&segment); 187 gst_pad_send_event(info->pad_sink, event); 188 189 pa_log_info("GStreamer pipeline initialisation succeeded"); 190 191 return true; 192 193pipeline_fail: 194 gst_deinit_common(info); 195 196 pa_log_error("GStreamer pipeline initialisation failed"); 197 198 return false; 199 200common_fail: 201 /* If common initialization fails the bin has not yet had its ownership 202 * transferred to the pipeline yet. 203 */ 204 gst_object_unref(transcoder); 205 206 pa_log_error("GStreamer pipeline creation failed"); 207 208 return false; 209} 210 211size_t gst_transcode_buffer(void *codec_info, uint32_t timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size, size_t *processed) { 212 struct gst_info *info = (struct gst_info *) codec_info; 213 gsize transcoded; 214 GstBuffer *in_buf; 215 GstFlowReturn ret; 216 size_t written = 0; 217 GstSample *sample; 218 219 pa_assert(info->pad_sink); 220 221 in_buf = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, 222 (gpointer)input_buffer, input_size, 0, input_size, NULL, NULL); 223 pa_assert(in_buf); 224 /* Acquire an extra reference to validate refcount afterwards */ 225 gst_mini_object_ref(GST_MINI_OBJECT_CAST(in_buf)); 226 pa_assert(GST_MINI_OBJECT_REFCOUNT_VALUE(in_buf) == 2); 227 228 if (timestamp == -1) 229 GST_BUFFER_TIMESTAMP(in_buf) = GST_CLOCK_TIME_NONE; 230 else { 231 // Timestamp is monotonically increasing with samplerate/packets-per-second; 232 // convert it to a timestamp in nanoseconds: 233 GST_BUFFER_TIMESTAMP(in_buf) = timestamp * PA_USEC_PER_SEC / info->ss->rate; 234 } 235 236 ret = gst_pad_chain(info->pad_sink, in_buf); 237 /** 238 * Ensure we're the only one holding a reference to this buffer after gst_pad_chain, 239 * which internally holds a pointer reference to input_buffer. The caller provides 240 * no guarantee to the validity of this pointer after returning from this function. 241 */ 242 pa_assert(GST_MINI_OBJECT_REFCOUNT_VALUE(in_buf) == 1); 243 gst_mini_object_unref(GST_MINI_OBJECT_CAST(in_buf)); 244 245 if (ret != GST_FLOW_OK) { 246 pa_log_error("failed to push buffer for transcoding %d", ret); 247 goto fail; 248 } 249 250 while ((sample = gst_app_sink_try_pull_sample(GST_APP_SINK(info->app_sink), 0))) { 251 in_buf = gst_sample_get_buffer(sample); 252 253 transcoded = gst_buffer_get_size(in_buf); 254 written += transcoded; 255 pa_assert(written <= output_size); 256 257 GstMapInfo map_info; 258 pa_assert_se(gst_buffer_map(in_buf, &map_info, GST_MAP_READ)); 259 memcpy(output_buffer, map_info.data, transcoded); 260 gst_buffer_unmap(in_buf, &map_info); 261 gst_sample_unref(sample); 262 } 263 264 *processed = input_size; 265 266 return written; 267 268fail: 269 *processed = 0; 270 271 return written; 272} 273 274void gst_codec_deinit(void *codec_info) { 275 struct gst_info *info = (struct gst_info *) codec_info; 276 277 if (info->bin) { 278 gst_element_set_state(info->bin, GST_STATE_NULL); 279 gst_object_unref(info->bin); 280 } 281 282 if (info->pad_sink) 283 gst_object_unref(GST_OBJECT(info->pad_sink)); 284 285 pa_xfree(info); 286} 287