diff options
author | Martin Schanzenbach <schanzen@gnunet.org> | 2023-10-19 11:36:32 +0200 |
---|---|---|
committer | Martin Schanzenbach <schanzen@gnunet.org> | 2023-10-19 11:36:32 +0200 |
commit | b56e4e05ad919c7191260fcf1d78b1f8d739871a (patch) | |
tree | 88a8aa4c82d664666c10c5099654bbfc295ab139 /src/conversation/gnunet-helper-audio-record-gst.c | |
parent | 7c7d819e8e03dadb91935d5ae91aa921cc7b86c7 (diff) | |
download | gnunet-b56e4e05ad919c7191260fcf1d78b1f8d739871a.tar.gz gnunet-b56e4e05ad919c7191260fcf1d78b1f8d739871a.zip |
BUILD: Move conversation to contrib/service
Diffstat (limited to 'src/conversation/gnunet-helper-audio-record-gst.c')
-rw-r--r-- | src/conversation/gnunet-helper-audio-record-gst.c | 393 |
1 files changed, 0 insertions, 393 deletions
diff --git a/src/conversation/gnunet-helper-audio-record-gst.c b/src/conversation/gnunet-helper-audio-record-gst.c deleted file mode 100644 index 883dd9eea..000000000 --- a/src/conversation/gnunet-helper-audio-record-gst.c +++ /dev/null | |||
@@ -1,393 +0,0 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2013 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | /** | ||
21 | * @file conversation/gnunet-helper-audio-record-gst.c | ||
22 | * @brief program to record audio data from the microphone (GStreamer version) | ||
23 | * @author LRN | ||
24 | */ | ||
25 | #include "platform.h" | ||
26 | #include "gnunet_util_lib.h" | ||
27 | #include "gnunet_protocols.h" | ||
28 | #include "conversation.h" | ||
29 | #include "gnunet_constants.h" | ||
30 | #include "gnunet_core_service.h" | ||
31 | |||
32 | #include <gst/gst.h> | ||
33 | #include <gst/app/gstappsink.h> | ||
34 | #include <gst/audio/gstaudiobasesrc.h> | ||
35 | #include <glib.h> | ||
36 | |||
37 | #define DEBUG_RECORD_PURE_OGG 1 | ||
38 | |||
39 | /** | ||
40 | * Number of channels. | ||
41 | * Must be one of the following (from libopusenc documentation): | ||
42 | * 1, 2 | ||
43 | */ | ||
44 | #define OPUS_CHANNELS 1 | ||
45 | |||
46 | /** | ||
47 | * Maximal size of a single opus packet. | ||
48 | */ | ||
49 | #define MAX_PAYLOAD_SIZE (1024 / OPUS_CHANNELS) | ||
50 | |||
51 | /** | ||
52 | * Size of a single frame fed to the encoder, in ms. | ||
53 | * Must be one of the following (from libopus documentation): | ||
54 | * 2.5, 5, 10, 20, 40 or 60 | ||
55 | */ | ||
56 | #define OPUS_FRAME_SIZE 40 | ||
57 | |||
58 | /** | ||
59 | * Expected packet loss to prepare for, in percents. | ||
60 | */ | ||
61 | #define PACKET_LOSS_PERCENTAGE 1 | ||
62 | |||
63 | /** | ||
64 | * Set to 1 to enable forward error correction. | ||
65 | * Set to 0 to disable. | ||
66 | */ | ||
67 | #define INBAND_FEC_MODE 1 | ||
68 | |||
69 | /** | ||
70 | * Max number of microseconds to buffer in audiosource. | ||
71 | * Default is 200000 | ||
72 | */ | ||
73 | #define BUFFER_TIME 1000 /* 1ms */ | ||
74 | |||
75 | /** | ||
76 | * Min number of microseconds to buffer in audiosource. | ||
77 | * Default is 10000 | ||
78 | */ | ||
79 | #define LATENCY_TIME 1000 /* 1ms */ | ||
80 | |||
81 | /** | ||
82 | * Maximum delay in multiplexing streams, in ns. | ||
83 | * Setting this to 0 forces page flushing, which | ||
84 | * decreases delay, but increases overhead. | ||
85 | */ | ||
86 | #define OGG_MAX_DELAY 0 | ||
87 | |||
88 | /** | ||
89 | * Maximum delay for sending out a page, in ns. | ||
90 | * Setting this to 0 forces page flushing, which | ||
91 | * decreases delay, but increases overhead. | ||
92 | */ | ||
93 | #define OGG_MAX_PAGE_DELAY 0 | ||
94 | |||
95 | /** | ||
96 | * Main pipeline. | ||
97 | */ | ||
98 | static GstElement *pipeline; | ||
99 | |||
100 | #ifdef DEBUG_RECORD_PURE_OGG | ||
101 | static int dump_pure_ogg; | ||
102 | #endif | ||
103 | |||
104 | static void | ||
105 | quit () | ||
106 | { | ||
107 | if (NULL != pipeline) | ||
108 | gst_element_set_state (pipeline, GST_STATE_NULL); | ||
109 | } | ||
110 | |||
111 | |||
112 | static gboolean | ||
113 | bus_call (GstBus *bus, GstMessage *msg, gpointer data) | ||
114 | { | ||
115 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bus message\n"); | ||
116 | switch (GST_MESSAGE_TYPE (msg)) | ||
117 | { | ||
118 | case GST_MESSAGE_EOS: | ||
119 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of stream\n"); | ||
120 | quit (); | ||
121 | break; | ||
122 | |||
123 | case GST_MESSAGE_ERROR: | ||
124 | { | ||
125 | gchar *debug; | ||
126 | GError *error; | ||
127 | |||
128 | gst_message_parse_error (msg, &error, &debug); | ||
129 | g_free (debug); | ||
130 | |||
131 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: %s\n", error->message); | ||
132 | g_error_free (error); | ||
133 | |||
134 | quit (); | ||
135 | break; | ||
136 | } | ||
137 | |||
138 | default: | ||
139 | break; | ||
140 | } | ||
141 | |||
142 | return TRUE; | ||
143 | } | ||
144 | |||
145 | |||
146 | void | ||
147 | source_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name, | ||
148 | gpointer user_data) | ||
149 | { | ||
150 | if (GST_IS_AUDIO_BASE_SRC (object)) | ||
151 | g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time", | ||
152 | (gint64) LATENCY_TIME, NULL); | ||
153 | } | ||
154 | |||
155 | |||
156 | static void | ||
157 | signalhandler (int s) | ||
158 | { | ||
159 | quit (); | ||
160 | } | ||
161 | |||
162 | |||
163 | int | ||
164 | main (int argc, char **argv) | ||
165 | { | ||
166 | GstElement *source, *filter, *encoder, *conv, *resampler, *sink, *oggmux; | ||
167 | GstCaps *caps; | ||
168 | GstBus *bus; | ||
169 | guint bus_watch_id; | ||
170 | struct AudioMessage audio_message; | ||
171 | int abort_send = 0; | ||
172 | |||
173 | typedef void (*SignalHandlerPointer) (int); | ||
174 | |||
175 | SignalHandlerPointer inthandler, termhandler; | ||
176 | inthandler = signal (SIGINT, signalhandler); | ||
177 | termhandler = signal (SIGTERM, signalhandler); | ||
178 | |||
179 | #ifdef DEBUG_RECORD_PURE_OGG | ||
180 | dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0; | ||
181 | #endif | ||
182 | |||
183 | /* Initialisation */ | ||
184 | gst_init (&argc, &argv); | ||
185 | |||
186 | GNUNET_assert (GNUNET_OK == | ||
187 | GNUNET_log_setup ("gnunet-helper-audio-record", | ||
188 | "WARNING", | ||
189 | NULL)); | ||
190 | |||
191 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
192 | "Audio source starts\n"); | ||
193 | |||
194 | audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); | ||
195 | |||
196 | /* Create gstreamer elements */ | ||
197 | pipeline = gst_pipeline_new ("audio-recorder"); | ||
198 | source = gst_element_factory_make ("autoaudiosrc", "audiosource"); | ||
199 | filter = gst_element_factory_make ("capsfilter", "filter"); | ||
200 | conv = gst_element_factory_make ("audioconvert", "converter"); | ||
201 | resampler = gst_element_factory_make ("audioresample", "resampler"); | ||
202 | encoder = gst_element_factory_make ("opusenc", "opus-encoder"); | ||
203 | oggmux = gst_element_factory_make ("oggmux", "ogg-muxer"); | ||
204 | sink = gst_element_factory_make ("appsink", "audio-output"); | ||
205 | |||
206 | if (! pipeline || ! filter || ! source || ! conv || ! resampler || | ||
207 | ! encoder || ! oggmux || ! sink) | ||
208 | { | ||
209 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
210 | "One element could not be created. Exiting.\n"); | ||
211 | return -1; | ||
212 | } | ||
213 | |||
214 | g_signal_connect (source, "child-added", G_CALLBACK (source_child_added), | ||
215 | NULL); | ||
216 | |||
217 | /* Set up the pipeline */ | ||
218 | |||
219 | caps = gst_caps_new_simple ("audio/x-raw", | ||
220 | "format", G_TYPE_STRING, "S16LE", | ||
221 | /* "rate", G_TYPE_INT, SAMPLING_RATE,*/ | ||
222 | "channels", G_TYPE_INT, OPUS_CHANNELS, | ||
223 | /* "layout", G_TYPE_STRING, "interleaved",*/ | ||
224 | NULL); | ||
225 | g_object_set (G_OBJECT (filter), | ||
226 | "caps", caps, | ||
227 | NULL); | ||
228 | gst_caps_unref (caps); | ||
229 | |||
230 | g_object_set (G_OBJECT (encoder), | ||
231 | /* "bitrate", 64000, */ | ||
232 | /* "bandwidth", OPUS_BANDWIDTH_FULLBAND, */ | ||
233 | "inband-fec", INBAND_FEC_MODE, | ||
234 | "packet-loss-percentage", PACKET_LOSS_PERCENTAGE, | ||
235 | "max-payload-size", MAX_PAYLOAD_SIZE, | ||
236 | "audio", FALSE, /* VoIP, not audio */ | ||
237 | "frame-size", OPUS_FRAME_SIZE, | ||
238 | NULL); | ||
239 | |||
240 | g_object_set (G_OBJECT (oggmux), | ||
241 | "max-delay", OGG_MAX_DELAY, | ||
242 | "max-page-delay", OGG_MAX_PAGE_DELAY, | ||
243 | NULL); | ||
244 | |||
245 | /* we add a message handler */ | ||
246 | bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); | ||
247 | bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline); | ||
248 | gst_object_unref (bus); | ||
249 | |||
250 | /* we add all elements into the pipeline */ | ||
251 | /* audiosource | converter | resampler | opus-encoder | audio-output */ | ||
252 | gst_bin_add_many (GST_BIN (pipeline), source, filter, conv, resampler, | ||
253 | encoder, | ||
254 | oggmux, sink, NULL); | ||
255 | |||
256 | /* we link the elements together */ | ||
257 | gst_element_link_many (source, filter, conv, resampler, encoder, oggmux, sink, | ||
258 | NULL); | ||
259 | |||
260 | /* Set the pipeline to "playing" state*/ | ||
261 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n"); | ||
262 | gst_element_set_state (pipeline, GST_STATE_PLAYING); | ||
263 | |||
264 | |||
265 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n"); | ||
266 | /* Iterate */ | ||
267 | while (! abort_send) | ||
268 | { | ||
269 | GstSample *s; | ||
270 | GstBuffer *b; | ||
271 | GstMapInfo m; | ||
272 | size_t len, msg_size; | ||
273 | const char *ptr; | ||
274 | int phase; | ||
275 | |||
276 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulling...\n"); | ||
277 | s = gst_app_sink_pull_sample (GST_APP_SINK (sink)); | ||
278 | if (NULL == s) | ||
279 | { | ||
280 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulled NULL\n"); | ||
281 | break; | ||
282 | } | ||
283 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "...pulled!\n"); | ||
284 | { | ||
285 | const GstStructure *si; | ||
286 | char *si_str; | ||
287 | GstCaps *s_caps; | ||
288 | char *caps_str; | ||
289 | si = gst_sample_get_info (s); | ||
290 | if (si) | ||
291 | { | ||
292 | si_str = gst_structure_to_string (si); | ||
293 | if (si_str) | ||
294 | { | ||
295 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample %s\n", si_str); | ||
296 | g_free (si_str); | ||
297 | } | ||
298 | } | ||
299 | else | ||
300 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no info\n"); | ||
301 | s_caps = gst_sample_get_caps (s); | ||
302 | if (s_caps) | ||
303 | { | ||
304 | caps_str = gst_caps_to_string (s_caps); | ||
305 | if (caps_str) | ||
306 | { | ||
307 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with caps %s\n", | ||
308 | caps_str); | ||
309 | g_free (caps_str); | ||
310 | } | ||
311 | } | ||
312 | else | ||
313 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no caps\n"); | ||
314 | } | ||
315 | b = gst_sample_get_buffer (s); | ||
316 | if ((NULL == b) || ! gst_buffer_map (b, &m, GST_MAP_READ)) | ||
317 | { | ||
318 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
319 | "got NULL buffer %p or failed to map the buffer\n", b); | ||
320 | gst_sample_unref (s); | ||
321 | continue; | ||
322 | } | ||
323 | |||
324 | len = m.size; | ||
325 | if (len > UINT16_MAX - sizeof(struct AudioMessage)) | ||
326 | { | ||
327 | GNUNET_break (0); | ||
328 | len = UINT16_MAX - sizeof(struct AudioMessage); | ||
329 | } | ||
330 | msg_size = sizeof(struct AudioMessage) + len; | ||
331 | audio_message.header.size = htons ((uint16_t) msg_size); | ||
332 | |||
333 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
334 | "Sending %u bytes of audio data\n", (unsigned int) msg_size); | ||
335 | for (phase = 0; phase < 2; phase++) | ||
336 | { | ||
337 | size_t offset; | ||
338 | size_t to_send; | ||
339 | ssize_t ret; | ||
340 | if (0 == phase) | ||
341 | { | ||
342 | #ifdef DEBUG_RECORD_PURE_OGG | ||
343 | if (dump_pure_ogg) | ||
344 | continue; | ||
345 | #endif | ||
346 | ptr = (const char *) &audio_message; | ||
347 | to_send = sizeof(audio_message); | ||
348 | } | ||
349 | else | ||
350 | { | ||
351 | ptr = (const char *) m.data; | ||
352 | to_send = len; | ||
353 | } | ||
354 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
355 | "Sending %u bytes on phase %d\n", (unsigned int) to_send, | ||
356 | phase); | ||
357 | for (offset = 0; offset < to_send; offset += ret) | ||
358 | { | ||
359 | ret = write (1, &ptr[offset], to_send - offset); | ||
360 | if (0 >= ret) | ||
361 | { | ||
362 | if (-1 == ret) | ||
363 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
364 | "Failed to write %u bytes at offset %u (total %u) in phase %d: %s\n", | ||
365 | (unsigned int) (to_send - offset), | ||
366 | (unsigned int) offset, | ||
367 | (unsigned int) (to_send + offset), | ||
368 | phase, | ||
369 | strerror (errno)); | ||
370 | abort_send = 1; | ||
371 | break; | ||
372 | } | ||
373 | } | ||
374 | if (abort_send) | ||
375 | break; | ||
376 | } | ||
377 | gst_buffer_unmap (b, &m); | ||
378 | gst_sample_unref (s); | ||
379 | } | ||
380 | |||
381 | signal (SIGINT, inthandler); | ||
382 | signal (SIGINT, termhandler); | ||
383 | |||
384 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returned, stopping playback\n"); | ||
385 | quit (); | ||
386 | |||
387 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting pipeline\n"); | ||
388 | gst_object_unref (GST_OBJECT (pipeline)); | ||
389 | pipeline = NULL; | ||
390 | g_source_remove (bus_watch_id); | ||
391 | |||
392 | return 0; | ||
393 | } | ||