diff options
author | LRN <lrn1986@gmail.com> | 2014-01-08 14:14:44 +0000 |
---|---|---|
committer | LRN <lrn1986@gmail.com> | 2014-01-08 14:14:44 +0000 |
commit | 73bbb9dfcfaa75720f90d35f4f9e9bf731ea9bc5 (patch) | |
tree | 3e6090b210a75efeaae2e624abfe53a71230ca64 /src/conversation | |
parent | 39d0485fb4ec8cb5b3142c86130b276ed455cb65 (diff) | |
download | gnunet-73bbb9dfcfaa75720f90d35f4f9e9bf731ea9bc5.tar.gz gnunet-73bbb9dfcfaa75720f90d35f4f9e9bf731ea9bc5.zip |
Add GStreamer-based implementation of conversation helpers
Diffstat (limited to 'src/conversation')
-rw-r--r-- | src/conversation/Makefile.am | 58 | ||||
-rwxr-xr-x | src/conversation/gnunet-helper-audio-playback-gst.c | 372 | ||||
-rwxr-xr-x | src/conversation/gnunet-helper-audio-record-gst.c | 334 |
3 files changed, 752 insertions, 12 deletions
diff --git a/src/conversation/Makefile.am b/src/conversation/Makefile.am index e455ba45e..a11763cc5 100644 --- a/src/conversation/Makefile.am +++ b/src/conversation/Makefile.am | |||
@@ -2,6 +2,10 @@ SUBDIRS = . | |||
2 | 2 | ||
3 | plugindir = $(libdir)/gnunet | 3 | plugindir = $(libdir)/gnunet |
4 | 4 | ||
5 | if MINGW | ||
6 | WINFLAGS = -no-undefined -Wl,--export-all-symbols | ||
7 | endif | ||
8 | |||
5 | AM_CPPFLAGS = \ | 9 | AM_CPPFLAGS = \ |
6 | $(GNUNET_CPPFLAGS) \ | 10 | $(GNUNET_CPPFLAGS) \ |
7 | -I$(top_srcdir)/src/include \ | 11 | -I$(top_srcdir)/src/include \ |
@@ -69,26 +73,30 @@ bin_PROGRAMS = \ | |||
69 | libexec_PROGRAMS = \ | 73 | libexec_PROGRAMS = \ |
70 | gnunet-service-conversation | 74 | gnunet-service-conversation |
71 | 75 | ||
72 | if HAVE_PULSE | ||
73 | if HAVE_OPUS | ||
74 | libexec_PROGRAMS += \ | ||
75 | gnunet-helper-audio-record \ | ||
76 | gnunet-helper-audio-playback | ||
77 | endif | ||
78 | endif | ||
79 | |||
80 | |||
81 | check_PROGRAMS = \ | 76 | check_PROGRAMS = \ |
82 | test_conversation_api \ | 77 | test_conversation_api \ |
83 | test_conversation_api_reject \ | 78 | test_conversation_api_reject \ |
84 | test_conversation_api_twocalls | 79 | test_conversation_api_twocalls |
85 | 80 | ||
86 | if HAVE_PULSE | 81 | if BUILD_PULSE_HELPERS |
87 | if HAVE_OPUS | 82 | AUDIO_HELPER_RECD=gnunet-helper-audio-record |
88 | TESTS = $(check_PROGRAMS) | 83 | AUDIO_HELPER_PLAY=gnunet-helper-audio-playback |
84 | AUDIO_TESTS=$(check_PROGRAMS) | ||
85 | else | ||
86 | if BUILD_GST_HELPERS | ||
87 | AUDIO_HELPER_RECD=gnunet-helper-audio-record | ||
88 | AUDIO_HELPER_PLAY=gnunet-helper-audio-playback | ||
89 | AUDIO_TESTS=$(check_PROGRAMS) | ||
89 | endif | 90 | endif |
90 | endif | 91 | endif |
91 | 92 | ||
93 | libexec_PROGRAMS += \ | ||
94 | $(AUDIO_HELPER_RECD) \ | ||
95 | $(AUDIO_HELPER_PLAY) | ||
96 | |||
97 | TESTS = $(AUDIO_TESTS) | ||
98 | |||
99 | if BUILD_PULSE_HELPERS | ||
92 | gnunet_helper_audio_record_SOURCES = \ | 100 | gnunet_helper_audio_record_SOURCES = \ |
93 | gnunet-helper-audio-record.c | 101 | gnunet-helper-audio-record.c |
94 | gnunet_helper_audio_record_LDADD = \ | 102 | gnunet_helper_audio_record_LDADD = \ |
@@ -106,6 +114,32 @@ gnunet_helper_audio_playback_LDADD = \ | |||
106 | $(INTLLIBS) | 114 | $(INTLLIBS) |
107 | gnunet_helper_audio_playback_LDFLAGS = \ | 115 | gnunet_helper_audio_playback_LDFLAGS = \ |
108 | $(GNUNET_LDFLAGS) $(WINFLAGS) | 116 | $(GNUNET_LDFLAGS) $(WINFLAGS) |
117 | else | ||
118 | if BUILD_GST_HELPERS | ||
119 | gnunet_helper_audio_record_SOURCES = \ | ||
120 | gnunet-helper-audio-record-gst.c | ||
121 | gnunet_helper_audio_record_LDADD = \ | ||
122 | $(top_builddir)/src/util/libgnunetutil.la \ | ||
123 | $(GST_LIBS) \ | ||
124 | $(INTLLIBS) | ||
125 | gnunet_helper_audio_record_LDFLAGS = \ | ||
126 | $(GNUNET_LDFLAGS) $(WINFLAGS) $(GST_LDFLAGS) | ||
127 | gnunet_helper_audio_record_CFLAGS = \ | ||
128 | $(GST_CFLAGS) | ||
129 | |||
130 | gnunet_helper_audio_playback_SOURCES = \ | ||
131 | gnunet-helper-audio-playback-gst.c | ||
132 | gnunet_helper_audio_playback_LDADD = \ | ||
133 | $(top_builddir)/src/util/libgnunetutil.la \ | ||
134 | -lopus \ | ||
135 | $(GST_LIBS) \ | ||
136 | $(INTLLIBS) | ||
137 | gnunet_helper_audio_playback_LDFLAGS = \ | ||
138 | $(GNUNET_LDFLAGS) $(WINFLAGS) $(GST_LDFLAGS) | ||
139 | gnunet_helper_audio_playback_CFLAGS = \ | ||
140 | $(GST_CFLAGS) | ||
141 | endif | ||
142 | endif | ||
109 | 143 | ||
110 | gnunet_service_conversation_SOURCES = \ | 144 | gnunet_service_conversation_SOURCES = \ |
111 | gnunet-service-conversation.c | 145 | gnunet-service-conversation.c |
diff --git a/src/conversation/gnunet-helper-audio-playback-gst.c b/src/conversation/gnunet-helper-audio-playback-gst.c new file mode 100755 index 000000000..d6d2316fc --- /dev/null +++ b/src/conversation/gnunet-helper-audio-playback-gst.c | |||
@@ -0,0 +1,372 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2013 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 3, or (at your | ||
8 | 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 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | /** | ||
21 | * @file conversation/gnunet-helper-audio-playback-gst.c | ||
22 | * @brief program to playback audio data to the speaker (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/gstappsrc.h> | ||
34 | #include <gst/audio/gstaudiobasesrc.h> | ||
35 | #include <glib.h> | ||
36 | |||
37 | #include <opus/opus.h> | ||
38 | #include <opus/opus_types.h> | ||
39 | |||
40 | /** | ||
41 | * How much data to read in one go | ||
42 | */ | ||
43 | #define MAXLINE 4096 | ||
44 | |||
45 | #define SAMPLING_RATE 48000 | ||
46 | |||
47 | #define CHANNELS 1 | ||
48 | |||
49 | #define FRAME_SIZE (SAMPLING_RATE / 50) | ||
50 | |||
51 | #define PCM_LENGTH (FRAME_SIZE * CHANNELS * sizeof (int16_t)) | ||
52 | |||
53 | /** | ||
54 | * Max number of microseconds to buffer in audiosink. | ||
55 | * Default is 200000 | ||
56 | */ | ||
57 | #define BUFFER_TIME 1000 | ||
58 | |||
59 | /** | ||
60 | * Min number of microseconds to buffer in audiosink. | ||
61 | * Default is 10000 | ||
62 | */ | ||
63 | #define LATENCY_TIME 1000 | ||
64 | |||
65 | /** | ||
66 | * Tokenizer for the data we get from stdin | ||
67 | */ | ||
68 | struct GNUNET_SERVER_MessageStreamTokenizer *stdin_mst; | ||
69 | |||
70 | /** | ||
71 | * Main pipeline. | ||
72 | */ | ||
73 | static GstElement *pipeline; | ||
74 | |||
75 | /** | ||
76 | * Appsrc instance into which we write data for the pipeline. | ||
77 | */ | ||
78 | static GstElement *source; | ||
79 | |||
80 | /** | ||
81 | * OPUS decoder | ||
82 | */ | ||
83 | static OpusDecoder *dec; | ||
84 | |||
85 | |||
86 | /** | ||
87 | * Set to 1 to break the reading loop | ||
88 | */ | ||
89 | static int abort_read; | ||
90 | |||
91 | |||
92 | /** | ||
93 | * OPUS initialization | ||
94 | */ | ||
95 | static void | ||
96 | opus_init () | ||
97 | { | ||
98 | int err; | ||
99 | int channels = 1; | ||
100 | |||
101 | dec = opus_decoder_create (SAMPLING_RATE, channels, &err); | ||
102 | } | ||
103 | |||
104 | void | ||
105 | sink_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name, gpointer user_data) | ||
106 | { | ||
107 | if (GST_IS_AUDIO_BASE_SRC (object)) | ||
108 | g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time", (gint64) LATENCY_TIME, NULL); | ||
109 | } | ||
110 | |||
111 | static void | ||
112 | quit () | ||
113 | { | ||
114 | if (NULL != source) | ||
115 | gst_app_src_end_of_stream (GST_APP_SRC (source)); | ||
116 | if (NULL != pipeline) | ||
117 | gst_element_set_state (pipeline, GST_STATE_NULL); | ||
118 | abort_read = 1; | ||
119 | } | ||
120 | |||
121 | static gboolean | ||
122 | bus_call (GstBus *bus, GstMessage *msg, gpointer data) | ||
123 | { | ||
124 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bus message\n"); | ||
125 | switch (GST_MESSAGE_TYPE (msg)) | ||
126 | { | ||
127 | case GST_MESSAGE_EOS: | ||
128 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of stream\n"); | ||
129 | quit (); | ||
130 | break; | ||
131 | |||
132 | case GST_MESSAGE_ERROR: | ||
133 | { | ||
134 | gchar *debug; | ||
135 | GError *error; | ||
136 | |||
137 | gst_message_parse_error (msg, &error, &debug); | ||
138 | g_free (debug); | ||
139 | |||
140 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: %s\n", error->message); | ||
141 | g_error_free (error); | ||
142 | |||
143 | quit (); | ||
144 | break; | ||
145 | } | ||
146 | default: | ||
147 | break; | ||
148 | } | ||
149 | |||
150 | return TRUE; | ||
151 | } | ||
152 | |||
153 | |||
154 | static void | ||
155 | signalhandler (int s) | ||
156 | { | ||
157 | quit (); | ||
158 | } | ||
159 | |||
160 | |||
161 | /** | ||
162 | * Message callback | ||
163 | */ | ||
164 | static int | ||
165 | stdin_receiver (void *cls, | ||
166 | void *client, | ||
167 | const struct GNUNET_MessageHeader *msg) | ||
168 | { | ||
169 | struct AudioMessage *audio; | ||
170 | GstBuffer *b; | ||
171 | int16_t *bufspace; | ||
172 | GstFlowReturn flow; | ||
173 | int ret; | ||
174 | |||
175 | switch (ntohs (msg->type)) | ||
176 | { | ||
177 | case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO: | ||
178 | audio = (struct AudioMessage *) msg; | ||
179 | |||
180 | bufspace = (int16_t *) g_malloc (PCM_LENGTH); | ||
181 | |||
182 | ret = opus_decode (dec, | ||
183 | (const unsigned char *) &audio[1], | ||
184 | ntohs (audio->header.size) - sizeof (struct AudioMessage), | ||
185 | bufspace, | ||
186 | FRAME_SIZE, 0); | ||
187 | if (ret < 0) | ||
188 | { | ||
189 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
190 | "Opus decoding failed: %d\n", | ||
191 | ret); | ||
192 | g_free (bufspace); | ||
193 | return GNUNET_OK; | ||
194 | } | ||
195 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
196 | "Decoded frame with %u bytes\n", | ||
197 | ntohs (audio->header.size)); | ||
198 | |||
199 | b = gst_buffer_new_wrapped (bufspace, ret * sizeof (int16_t)); | ||
200 | if (NULL == b) | ||
201 | { | ||
202 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to wrap a buffer\n"); | ||
203 | g_free (bufspace); | ||
204 | return GNUNET_SYSERR; | ||
205 | } | ||
206 | |||
207 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pushing...\n"); | ||
208 | flow = gst_app_src_push_buffer (GST_APP_SRC (source), b); | ||
209 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pushed!\n"); | ||
210 | /* They all return GNUNET_OK, because currently player stops when | ||
211 | * data stops coming. This might need to be changed for the player | ||
212 | * to also stop when pipeline breaks. | ||
213 | */ | ||
214 | switch (flow) | ||
215 | { | ||
216 | case GST_FLOW_OK: | ||
217 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Fed %u bytes to the pipeline\n", | ||
218 | (unsigned int) ret * sizeof (int16_t)); | ||
219 | break; | ||
220 | case GST_FLOW_FLUSHING: | ||
221 | /* buffer was dropped, because pipeline state is not PAUSED or PLAYING */ | ||
222 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Dropped a buffer\n"); | ||
223 | break; | ||
224 | case GST_FLOW_EOS: | ||
225 | /* end of stream */ | ||
226 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "EOS\n"); | ||
227 | break; | ||
228 | default: | ||
229 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unexpected push result\n"); | ||
230 | break; | ||
231 | } | ||
232 | break; | ||
233 | default: | ||
234 | break; | ||
235 | } | ||
236 | return GNUNET_OK; | ||
237 | } | ||
238 | |||
239 | |||
240 | int | ||
241 | main (int argc, char **argv) | ||
242 | { | ||
243 | GstElement *conv, *resampler, *sink; | ||
244 | GstBus *bus; | ||
245 | GstCaps *caps; | ||
246 | guint bus_watch_id; | ||
247 | uint64_t toff; | ||
248 | |||
249 | typedef void (*SignalHandlerPointer) (int); | ||
250 | |||
251 | SignalHandlerPointer inthandler, termhandler; | ||
252 | |||
253 | inthandler = signal (SIGINT, signalhandler); | ||
254 | termhandler = signal (SIGTERM, signalhandler); | ||
255 | |||
256 | #ifdef WINDOWS | ||
257 | setmode (0, _O_BINARY); | ||
258 | #endif | ||
259 | |||
260 | opus_init (); | ||
261 | |||
262 | /* Initialisation */ | ||
263 | gst_init (&argc, &argv); | ||
264 | |||
265 | GNUNET_assert (GNUNET_OK == | ||
266 | GNUNET_log_setup ("gnunet-helper-audio-playback", | ||
267 | "WARNING", | ||
268 | NULL)); | ||
269 | |||
270 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
271 | "Audio sink starts\n"); | ||
272 | |||
273 | stdin_mst = GNUNET_SERVER_mst_create (&stdin_receiver, NULL); | ||
274 | |||
275 | /* Create gstreamer elements */ | ||
276 | pipeline = gst_pipeline_new ("audio-player"); | ||
277 | source = gst_element_factory_make ("appsrc", "audio-input"); | ||
278 | conv = gst_element_factory_make ("audioconvert", "converter"); | ||
279 | resampler= gst_element_factory_make ("audioresample", "resampler"); | ||
280 | sink = gst_element_factory_make ("autoaudiosink", "audiosink"); | ||
281 | |||
282 | if (!pipeline || !source || !conv || !resampler || !sink) | ||
283 | { | ||
284 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
285 | "One element could not be created. Exiting.\n"); | ||
286 | return -1; | ||
287 | } | ||
288 | |||
289 | g_signal_connect (sink, "child-added", G_CALLBACK (sink_child_added), NULL); | ||
290 | |||
291 | caps = gst_caps_new_simple ("audio/x-raw", | ||
292 | "format", G_TYPE_STRING, "S16LE", | ||
293 | "rate", G_TYPE_INT, SAMPLING_RATE, | ||
294 | "channels", G_TYPE_INT, CHANNELS, | ||
295 | "layout", G_TYPE_STRING, "interleaved", | ||
296 | NULL); | ||
297 | gst_app_src_set_caps (GST_APP_SRC (source), caps); | ||
298 | gst_caps_unref (caps); | ||
299 | |||
300 | /* Keep a reference to it, we operate on it */ | ||
301 | gst_object_ref (GST_OBJECT (source)); | ||
302 | |||
303 | /* Set up the pipeline */ | ||
304 | |||
305 | /* we feed appsrc as fast as possible, it just blocks when it's full */ | ||
306 | g_object_set (G_OBJECT (source), | ||
307 | "format", GST_FORMAT_TIME, | ||
308 | "block", TRUE, | ||
309 | "is-live", TRUE, | ||
310 | NULL); | ||
311 | |||
312 | /* we add a message handler */ | ||
313 | bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); | ||
314 | bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline); | ||
315 | gst_object_unref (bus); | ||
316 | |||
317 | /* we add all elements into the pipeline */ | ||
318 | /* audio-input | converter | resampler | audiosink */ | ||
319 | gst_bin_add_many (GST_BIN (pipeline), source, conv, | ||
320 | resampler, sink, NULL); | ||
321 | |||
322 | /* we link the elements together */ | ||
323 | gst_element_link_many (source, conv, resampler, sink, NULL); | ||
324 | |||
325 | /* Set the pipeline to "playing" state*/ | ||
326 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n"); | ||
327 | gst_element_set_state (pipeline, GST_STATE_PLAYING); | ||
328 | |||
329 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n"); | ||
330 | /* Iterate */ | ||
331 | toff = 0; | ||
332 | while (!abort_read) | ||
333 | { | ||
334 | char readbuf[MAXLINE]; | ||
335 | int ret; | ||
336 | |||
337 | ret = read (0, readbuf, sizeof (readbuf)); | ||
338 | if (0 > ret) | ||
339 | { | ||
340 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
341 | _("Read error from STDIN: %d %s\n"), | ||
342 | ret, strerror (errno)); | ||
343 | break; | ||
344 | } | ||
345 | toff += ret; | ||
346 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
347 | "Received %d bytes of audio data (total: %llu)\n", | ||
348 | (int) ret, | ||
349 | toff); | ||
350 | if (0 == ret) | ||
351 | break; | ||
352 | GNUNET_SERVER_mst_receive (stdin_mst, NULL, | ||
353 | readbuf, ret, | ||
354 | GNUNET_NO, GNUNET_NO); | ||
355 | } | ||
356 | GNUNET_SERVER_mst_destroy (stdin_mst); | ||
357 | |||
358 | signal (SIGINT, inthandler); | ||
359 | signal (SIGINT, termhandler); | ||
360 | |||
361 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returned, stopping playback\n"); | ||
362 | quit (); | ||
363 | |||
364 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting pipeline\n"); | ||
365 | gst_object_unref (GST_OBJECT (source)); | ||
366 | source = NULL; | ||
367 | gst_object_unref (GST_OBJECT (pipeline)); | ||
368 | pipeline = NULL; | ||
369 | g_source_remove (bus_watch_id); | ||
370 | |||
371 | return 0; | ||
372 | } | ||
diff --git a/src/conversation/gnunet-helper-audio-record-gst.c b/src/conversation/gnunet-helper-audio-record-gst.c new file mode 100755 index 000000000..8d7a88fab --- /dev/null +++ b/src/conversation/gnunet-helper-audio-record-gst.c | |||
@@ -0,0 +1,334 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2013 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 3, or (at your | ||
8 | 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 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
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 | /** | ||
38 | * Number of channels. | ||
39 | * Must be one of the following (from libopusenc documentation): | ||
40 | * 1, 2 | ||
41 | */ | ||
42 | #define OPUS_CHANNELS 1 | ||
43 | |||
44 | /** | ||
45 | * Maximal size of a single opus packet. | ||
46 | */ | ||
47 | #define MAX_PAYLOAD_SIZE (1024 / OPUS_CHANNELS) | ||
48 | |||
49 | /** | ||
50 | * Size of a single frame fed to the encoder, in ms. | ||
51 | * Must be one of the following (from libopus documentation): | ||
52 | * 2.5, 5, 10, 20, 40 or 60 | ||
53 | */ | ||
54 | #define OPUS_FRAME_SIZE 20 | ||
55 | |||
56 | /** | ||
57 | * Expected packet loss to prepare for, in percents. | ||
58 | */ | ||
59 | #define PACKET_LOSS_PERCENTAGE 1 | ||
60 | |||
61 | /** | ||
62 | * Set to 1 to enable forward error correction. | ||
63 | * Set to 0 to disable. | ||
64 | */ | ||
65 | #define INBAND_FEC_MODE 1 | ||
66 | |||
67 | /** | ||
68 | * Max number of microseconds to buffer in audiosource. | ||
69 | * Default is 200000 | ||
70 | */ | ||
71 | #define BUFFER_TIME 1000 | ||
72 | |||
73 | /** | ||
74 | * Min number of microseconds to buffer in audiosource. | ||
75 | * Default is 10000 | ||
76 | */ | ||
77 | #define LATENCY_TIME 1000 | ||
78 | |||
79 | /** | ||
80 | * Main pipeline. | ||
81 | */ | ||
82 | static GstElement *pipeline; | ||
83 | |||
84 | static void | ||
85 | quit () | ||
86 | { | ||
87 | if (NULL != pipeline) | ||
88 | gst_element_set_state (pipeline, GST_STATE_NULL); | ||
89 | } | ||
90 | |||
91 | static gboolean | ||
92 | bus_call (GstBus *bus, GstMessage *msg, gpointer data) | ||
93 | { | ||
94 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bus message\n"); | ||
95 | switch (GST_MESSAGE_TYPE (msg)) | ||
96 | { | ||
97 | case GST_MESSAGE_EOS: | ||
98 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of stream\n"); | ||
99 | quit (); | ||
100 | break; | ||
101 | |||
102 | case GST_MESSAGE_ERROR: | ||
103 | { | ||
104 | gchar *debug; | ||
105 | GError *error; | ||
106 | |||
107 | gst_message_parse_error (msg, &error, &debug); | ||
108 | g_free (debug); | ||
109 | |||
110 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: %s\n", error->message); | ||
111 | g_error_free (error); | ||
112 | |||
113 | quit (); | ||
114 | break; | ||
115 | } | ||
116 | default: | ||
117 | break; | ||
118 | } | ||
119 | |||
120 | return TRUE; | ||
121 | } | ||
122 | |||
123 | void | ||
124 | source_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name, gpointer user_data) | ||
125 | { | ||
126 | if (GST_IS_AUDIO_BASE_SRC (object)) | ||
127 | g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time", (gint64) LATENCY_TIME, NULL); | ||
128 | } | ||
129 | |||
130 | static void | ||
131 | signalhandler (int s) | ||
132 | { | ||
133 | quit (); | ||
134 | } | ||
135 | |||
136 | |||
137 | int | ||
138 | main (int argc, char **argv) | ||
139 | { | ||
140 | GstElement *source, *encoder, *conv, *resampler, *sink; | ||
141 | GstBus *bus; | ||
142 | guint bus_watch_id; | ||
143 | struct AudioMessage audio_message; | ||
144 | int abort_send = 0; | ||
145 | |||
146 | typedef void (*SignalHandlerPointer) (int); | ||
147 | |||
148 | SignalHandlerPointer inthandler, termhandler; | ||
149 | inthandler = signal (SIGINT, signalhandler); | ||
150 | termhandler = signal (SIGTERM, signalhandler); | ||
151 | |||
152 | #ifdef WINDOWS | ||
153 | setmode (1, _O_BINARY); | ||
154 | #endif | ||
155 | |||
156 | /* Initialisation */ | ||
157 | gst_init (&argc, &argv); | ||
158 | |||
159 | GNUNET_assert (GNUNET_OK == | ||
160 | GNUNET_log_setup ("gnunet-helper-audio-record", | ||
161 | "WARNING", | ||
162 | NULL)); | ||
163 | |||
164 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
165 | "Audio source starts\n"); | ||
166 | |||
167 | audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); | ||
168 | |||
169 | /* Create gstreamer elements */ | ||
170 | pipeline = gst_pipeline_new ("audio-recorder"); | ||
171 | source = gst_element_factory_make ("autoaudiosrc", "audiosource"); | ||
172 | conv = gst_element_factory_make ("audioconvert", "converter"); | ||
173 | resampler= gst_element_factory_make ("audioresample", "resampler"); | ||
174 | encoder = gst_element_factory_make ("opusenc", "opus-encoder"); | ||
175 | sink = gst_element_factory_make ("appsink", "audio-output"); | ||
176 | |||
177 | if (!pipeline || !source || !conv || !resampler || !encoder || !sink) | ||
178 | { | ||
179 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
180 | "One element could not be created. Exiting.\n"); | ||
181 | return -1; | ||
182 | } | ||
183 | |||
184 | g_signal_connect (source, "child-added", G_CALLBACK (source_child_added), NULL); | ||
185 | |||
186 | /* Set up the pipeline */ | ||
187 | |||
188 | g_object_set (G_OBJECT (encoder), | ||
189 | /* "bitrate", 64000, */ | ||
190 | /* "bandwidth", OPUS_BANDWIDTH_FULLBAND, */ | ||
191 | "inband-fec", INBAND_FEC_MODE, | ||
192 | "packet-loss-percentage", PACKET_LOSS_PERCENTAGE, | ||
193 | "max-payload-size", MAX_PAYLOAD_SIZE, | ||
194 | "audio", FALSE, /* VoIP, not audio */ | ||
195 | "frame-size", OPUS_FRAME_SIZE, | ||
196 | NULL); | ||
197 | |||
198 | /* we add a message handler */ | ||
199 | bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); | ||
200 | bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline); | ||
201 | gst_object_unref (bus); | ||
202 | |||
203 | /* we add all elements into the pipeline */ | ||
204 | /* audiosource | converter | resampler | opus-encoder | audio-output */ | ||
205 | gst_bin_add_many (GST_BIN (pipeline), source, conv, resampler, encoder, | ||
206 | sink, NULL); | ||
207 | |||
208 | /* we link the elements together */ | ||
209 | gst_element_link_many (source, conv, resampler, encoder, sink, NULL); | ||
210 | |||
211 | /* Set the pipeline to "playing" state*/ | ||
212 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n"); | ||
213 | gst_element_set_state (pipeline, GST_STATE_PLAYING); | ||
214 | |||
215 | |||
216 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n"); | ||
217 | /* Iterate */ | ||
218 | while (!abort_send) | ||
219 | { | ||
220 | GstSample *s; | ||
221 | GstBuffer *b; | ||
222 | GstMapInfo m; | ||
223 | size_t len, msg_size; | ||
224 | const char *ptr; | ||
225 | int phase; | ||
226 | |||
227 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulling...\n"); | ||
228 | s = gst_app_sink_pull_sample (GST_APP_SINK (sink)); | ||
229 | if (NULL == s) | ||
230 | { | ||
231 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulled NULL\n"); | ||
232 | break; | ||
233 | } | ||
234 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "...pulled!\n"); | ||
235 | { | ||
236 | const GstStructure *si; | ||
237 | char *si_str; | ||
238 | GstCaps *s_caps; | ||
239 | char *caps_str; | ||
240 | si = gst_sample_get_info (s); | ||
241 | if (si) | ||
242 | { | ||
243 | si_str = gst_structure_to_string (si); | ||
244 | if (si_str) | ||
245 | { | ||
246 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample %s\n", si_str); | ||
247 | g_free (si_str); | ||
248 | } | ||
249 | } | ||
250 | else | ||
251 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no info\n"); | ||
252 | s_caps = gst_sample_get_caps (s); | ||
253 | if (s_caps) | ||
254 | { | ||
255 | caps_str = gst_caps_to_string (s_caps); | ||
256 | if (caps_str) | ||
257 | { | ||
258 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with caps %s\n", caps_str); | ||
259 | g_free (caps_str); | ||
260 | } | ||
261 | } | ||
262 | else | ||
263 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no caps\n"); | ||
264 | } | ||
265 | b = gst_sample_get_buffer (s); | ||
266 | if (NULL == b || !gst_buffer_map (b, &m, GST_MAP_READ)) | ||
267 | { | ||
268 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "got NULL buffer %p or failed to map the buffer\n", b); | ||
269 | gst_sample_unref (s); | ||
270 | continue; | ||
271 | } | ||
272 | |||
273 | len = m.size; | ||
274 | if (len > UINT16_MAX - sizeof (struct AudioMessage)) | ||
275 | { | ||
276 | GNUNET_break (0); | ||
277 | len = UINT16_MAX - sizeof (struct AudioMessage); | ||
278 | } | ||
279 | msg_size = sizeof (struct AudioMessage) + len; | ||
280 | audio_message.header.size = htons ((uint16_t) msg_size); | ||
281 | |||
282 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
283 | "Sending %u bytes of audio data\n", (unsigned int) msg_size); | ||
284 | for (phase = 0; phase < 2; phase++) | ||
285 | { | ||
286 | size_t offset; | ||
287 | size_t to_send; | ||
288 | ssize_t ret; | ||
289 | if (0 == phase) | ||
290 | { | ||
291 | ptr = (const char *) &audio_message; | ||
292 | to_send = sizeof (audio_message); | ||
293 | } | ||
294 | else | ||
295 | { | ||
296 | ptr = (const char *) m.data; | ||
297 | to_send = len; | ||
298 | } | ||
299 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
300 | "Sending %u bytes on phase %d\n", (unsigned int) to_send, phase); | ||
301 | for (offset = 0; offset < to_send; offset += ret) | ||
302 | { | ||
303 | ret = write (1, &ptr[offset], to_send - offset); | ||
304 | if (0 >= ret) | ||
305 | { | ||
306 | if (-1 == ret) | ||
307 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
308 | "Failed to write %u bytes at offset %u (total %u) in phase %d: %s\n", | ||
309 | (unsigned int) to_send - offset, (unsigned int) offset, | ||
310 | (unsigned int) (to_send + offset), phase, strerror (errno)); | ||
311 | abort_send = 1; | ||
312 | break; | ||
313 | } | ||
314 | } | ||
315 | if (abort_send) | ||
316 | break; | ||
317 | } | ||
318 | gst_buffer_unmap (b, &m); | ||
319 | gst_sample_unref (s); | ||
320 | } | ||
321 | |||
322 | signal (SIGINT, inthandler); | ||
323 | signal (SIGINT, termhandler); | ||
324 | |||
325 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returned, stopping playback\n"); | ||
326 | quit (); | ||
327 | |||
328 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting pipeline\n"); | ||
329 | gst_object_unref (GST_OBJECT (pipeline)); | ||
330 | pipeline = NULL; | ||
331 | g_source_remove (bus_watch_id); | ||
332 | |||
333 | return 0; | ||
334 | } | ||