• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
app_sink_eos(GstAppSink * appsink,gpointer userdata)40 static void app_sink_eos(GstAppSink *appsink, gpointer userdata) {
41     pa_log_debug("Sink got EOS");
42 }
43 
gst_deinit_common(struct gst_info * info)44 static 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 
gst_init_common(struct gst_info * info)53 bool 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 
76 fail:
77     if (appsink)
78         gst_object_unref(appsink);
79 
80     return false;
81 }
82 
gst_create_caps_from_sample_spec(const pa_sample_spec * ss)83 static 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 
gst_codec_init(struct gst_info * info,bool for_encoding,GstElement * transcoder)130 bool 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 
193 pipeline_fail:
194     gst_deinit_common(info);
195 
196     pa_log_error("GStreamer pipeline initialisation failed");
197 
198     return false;
199 
200 common_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 
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)211 size_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 
268 fail:
269     *processed = 0;
270 
271     return written;
272 }
273 
gst_codec_deinit(void * codec_info)274 void 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