diff options
author | Andreas Fuchs <fuchandr@in.tum.de> | 2013-10-01 11:34:38 +0000 |
---|---|---|
committer | Andreas Fuchs <fuchandr@in.tum.de> | 2013-10-01 11:34:38 +0000 |
commit | 70c7532c4d0c684afc4158984f9a37ae7cf05ba3 (patch) | |
tree | 693ef5a8b848db69c6e4b79c2d6f2a583f99db0c /src/conversation/gnunet-helper-audio-playback.c | |
parent | 408c3426ad378e8eef910ca6fea1144110346b0b (diff) | |
download | gnunet-70c7532c4d0c684afc4158984f9a37ae7cf05ba3.tar.gz gnunet-70c7532c4d0c684afc4158984f9a37ae7cf05ba3.zip |
Initial conversation (experimental) commit
Diffstat (limited to 'src/conversation/gnunet-helper-audio-playback.c')
-rw-r--r-- | src/conversation/gnunet-helper-audio-playback.c | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/src/conversation/gnunet-helper-audio-playback.c b/src/conversation/gnunet-helper-audio-playback.c new file mode 100644 index 000000000..6e8ca86ff --- /dev/null +++ b/src/conversation/gnunet-helper-audio-playback.c | |||
@@ -0,0 +1,391 @@ | |||
1 | #include <gnunet/platform.h> | ||
2 | #include <gnunet/gnunet_util_lib.h> | ||
3 | #include "gnunet_protocols_conversation.h" | ||
4 | #include <gnunet/gnunet_constants.h> | ||
5 | #include <gnunet/gnunet_core_service.h> | ||
6 | |||
7 | #include <pulse/simple.h> | ||
8 | #include <pulse/error.h> | ||
9 | #include <pulse/rtclock.h> | ||
10 | |||
11 | #include <pulse/pulseaudio.h> | ||
12 | #include <opus/opus.h> | ||
13 | #include <opus/opus_types.h> | ||
14 | |||
15 | #define MAXLINE 4096 | ||
16 | |||
17 | /** | ||
18 | * GNUnet Message Tokenizer | ||
19 | */ | ||
20 | #include "mst.c" | ||
21 | |||
22 | /** | ||
23 | * Pulseaudio specification. May change in the future. | ||
24 | */ | ||
25 | static pa_sample_spec sample_spec = { | ||
26 | .format = PA_SAMPLE_FLOAT32LE, | ||
27 | .rate = 48000, | ||
28 | .channels = 1 | ||
29 | }; | ||
30 | |||
31 | /** | ||
32 | * Pulseaudio mainloop api | ||
33 | */ | ||
34 | static pa_mainloop_api *mainloop_api = NULL; | ||
35 | |||
36 | /** | ||
37 | * Pulseaudio threaded mainloop | ||
38 | */ | ||
39 | static pa_threaded_mainloop *m = NULL; | ||
40 | |||
41 | /** | ||
42 | * Pulseaudio context | ||
43 | */ | ||
44 | static pa_context *context = NULL; | ||
45 | |||
46 | /** | ||
47 | * Pulseaudio output stream | ||
48 | */ | ||
49 | static pa_stream *stream_out = NULL; | ||
50 | |||
51 | /** | ||
52 | * Pulseaudio io events | ||
53 | */ | ||
54 | static pa_io_event *stdio_event = NULL; | ||
55 | |||
56 | /** | ||
57 | * OPUS decoder | ||
58 | */ | ||
59 | OpusDecoder *dec = NULL; | ||
60 | |||
61 | /** | ||
62 | * PCM data buffer | ||
63 | */ | ||
64 | float *pcm_buffer; | ||
65 | |||
66 | /** | ||
67 | * Length of PCM buffer | ||
68 | */ | ||
69 | int pcm_length; | ||
70 | |||
71 | /** | ||
72 | * Number of samples for one frame | ||
73 | */ | ||
74 | int frame_size; | ||
75 | |||
76 | /** | ||
77 | * The sampling rate used in Pulseaudio specification | ||
78 | */ | ||
79 | opus_int32 sampling_rate; | ||
80 | |||
81 | /** | ||
82 | * Audio buffer | ||
83 | */ | ||
84 | static void *buffer = NULL; | ||
85 | |||
86 | /** | ||
87 | * Length of audio buffer | ||
88 | */ | ||
89 | static size_t buffer_length = 0; | ||
90 | |||
91 | /** | ||
92 | * Read index for transmit buffer | ||
93 | */ | ||
94 | static size_t buffer_index = 0; | ||
95 | |||
96 | |||
97 | |||
98 | /** | ||
99 | * Message callback | ||
100 | */ | ||
101 | static void | ||
102 | stdin_receiver (void *cls, const struct GNUNET_MessageHeader *msg) | ||
103 | { | ||
104 | struct AudioMessage *audio; | ||
105 | |||
106 | switch (ntohs (msg->type)) | ||
107 | { | ||
108 | case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO: | ||
109 | audio = (struct AudioMessage *) msg; | ||
110 | |||
111 | int len = | ||
112 | opus_decode_float (dec, audio->audio, audio->length, pcm_buffer, | ||
113 | frame_size, 0); | ||
114 | |||
115 | if (pa_stream_write | ||
116 | (stream_out, (uint8_t *) pcm_buffer, pcm_length, NULL, 0, | ||
117 | PA_SEEK_RELATIVE) < 0) | ||
118 | { | ||
119 | |||
120 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
121 | _("pa_stream_write() failed: %s\n"), | ||
122 | pa_strerror (pa_context_errno (context))); | ||
123 | return; | ||
124 | } | ||
125 | |||
126 | break; | ||
127 | |||
128 | default: | ||
129 | break; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | /** | ||
134 | * Pulseaudio shutdown task | ||
135 | */ | ||
136 | static void | ||
137 | quit (int ret) | ||
138 | { | ||
139 | mainloop_api->quit (mainloop_api, ret); | ||
140 | exit (ret); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Write some data to the stream | ||
145 | */ | ||
146 | static void | ||
147 | do_stream_write (size_t length) | ||
148 | { | ||
149 | size_t l; | ||
150 | GNUNET_assert (length); | ||
151 | |||
152 | if (!buffer || !buffer_length) | ||
153 | { | ||
154 | return; | ||
155 | } | ||
156 | |||
157 | |||
158 | l = length; | ||
159 | if (l > buffer_length) | ||
160 | { | ||
161 | l = buffer_length; | ||
162 | |||
163 | } | ||
164 | |||
165 | if (pa_stream_write | ||
166 | (stream_out, (uint8_t *) buffer + buffer_index, l, NULL, 0, | ||
167 | PA_SEEK_RELATIVE) < 0) | ||
168 | { | ||
169 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
170 | _("pa_stream_write() failed: %s\n"), | ||
171 | pa_strerror (pa_context_errno (context))); | ||
172 | quit (1); | ||
173 | return; | ||
174 | } | ||
175 | |||
176 | buffer_length -= l; | ||
177 | buffer_index += l; | ||
178 | |||
179 | if (!buffer_length) | ||
180 | { | ||
181 | pa_xfree (buffer); | ||
182 | buffer = NULL; | ||
183 | buffer_index = buffer_length = 0; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Callback when data is there for playback | ||
189 | */ | ||
190 | static void | ||
191 | stream_write_callback (pa_stream * s, size_t length, void *userdata) | ||
192 | { | ||
193 | |||
194 | if (stdio_event) | ||
195 | { | ||
196 | mainloop_api->io_enable (stdio_event, PA_IO_EVENT_INPUT); | ||
197 | } | ||
198 | |||
199 | |||
200 | if (!buffer) | ||
201 | { | ||
202 | return; | ||
203 | } | ||
204 | |||
205 | |||
206 | do_stream_write (length); | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * Exit callback for SIGTERM and SIGINT | ||
211 | */ | ||
212 | static void | ||
213 | exit_signal_callback (pa_mainloop_api * m, pa_signal_event * e, int sig, | ||
214 | void *userdata) | ||
215 | { | ||
216 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
217 | _("gnunet-helper-audio-playback - Got signal, exiting\n")); | ||
218 | quit (1); | ||
219 | } | ||
220 | |||
221 | /** | ||
222 | * Pulseaudio stream state callback | ||
223 | */ | ||
224 | static void | ||
225 | context_state_callback (pa_context * c, void *userdata) | ||
226 | { | ||
227 | int p; | ||
228 | GNUNET_assert (c); | ||
229 | |||
230 | switch (pa_context_get_state (c)) | ||
231 | { | ||
232 | case PA_CONTEXT_CONNECTING: | ||
233 | case PA_CONTEXT_AUTHORIZING: | ||
234 | case PA_CONTEXT_SETTING_NAME: | ||
235 | break; | ||
236 | |||
237 | case PA_CONTEXT_READY: | ||
238 | { | ||
239 | GNUNET_assert (c); | ||
240 | GNUNET_assert (!stream_out); | ||
241 | |||
242 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Connection established.\n")); | ||
243 | |||
244 | |||
245 | if (! | ||
246 | (stream_out = | ||
247 | pa_stream_new (c, "GNUNET VoIP playback", &sample_spec, NULL))) | ||
248 | { | ||
249 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
250 | _("pa_stream_new() failed: %s\n"), | ||
251 | pa_strerror (pa_context_errno (c))); | ||
252 | goto fail; | ||
253 | } | ||
254 | |||
255 | pa_stream_set_write_callback (stream_out, stream_write_callback, | ||
256 | NULL); | ||
257 | |||
258 | if ((p = | ||
259 | pa_stream_connect_playback (stream_out, NULL, NULL, 0, NULL, | ||
260 | NULL)) < 0) | ||
261 | { | ||
262 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
263 | _("pa_stream_connect_playback() failed: %s\n"), | ||
264 | pa_strerror (pa_context_errno (c))); | ||
265 | goto fail; | ||
266 | } | ||
267 | |||
268 | break; | ||
269 | } | ||
270 | |||
271 | case PA_CONTEXT_TERMINATED: | ||
272 | quit (0); | ||
273 | break; | ||
274 | |||
275 | case PA_CONTEXT_FAILED: | ||
276 | default: | ||
277 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Connection failure: %s\n"), | ||
278 | pa_strerror (pa_context_errno (c))); | ||
279 | goto fail; | ||
280 | } | ||
281 | |||
282 | return; | ||
283 | |||
284 | fail: | ||
285 | quit (1); | ||
286 | |||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Pulseaudio initialization | ||
291 | */ | ||
292 | void | ||
293 | pa_init () | ||
294 | { | ||
295 | int r; | ||
296 | |||
297 | if (!pa_sample_spec_valid (&sample_spec)) | ||
298 | { | ||
299 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Wrong Spec\n")); | ||
300 | } | ||
301 | |||
302 | /* set up threaded playback mainloop */ | ||
303 | |||
304 | if (!(m = pa_threaded_mainloop_new ())) | ||
305 | { | ||
306 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_new() failed.\n")); | ||
307 | } | ||
308 | |||
309 | mainloop_api = pa_threaded_mainloop_get_api (m); | ||
310 | |||
311 | |||
312 | /* listen to signals */ | ||
313 | |||
314 | r = pa_signal_init (mainloop_api); | ||
315 | GNUNET_assert (r == 0); | ||
316 | pa_signal_new (SIGINT, exit_signal_callback, NULL); | ||
317 | pa_signal_new (SIGTERM, exit_signal_callback, NULL); | ||
318 | |||
319 | |||
320 | /* connect to the main pulseaudio context */ | ||
321 | |||
322 | if (!(context = pa_context_new (mainloop_api, "GNUnet VoIP"))) | ||
323 | { | ||
324 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_context_new() failed.\n")); | ||
325 | } | ||
326 | |||
327 | pa_context_set_state_callback (context, context_state_callback, NULL); | ||
328 | |||
329 | if (pa_context_connect (context, NULL, 0, NULL) < 0) | ||
330 | { | ||
331 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
332 | _("pa_context_connect() failed: %s\n"), | ||
333 | pa_strerror (pa_context_errno (context))); | ||
334 | } | ||
335 | |||
336 | if (pa_threaded_mainloop_start (m) < 0) | ||
337 | { | ||
338 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_run() failed.\n")); | ||
339 | } | ||
340 | } | ||
341 | |||
342 | /** | ||
343 | * OPUS initialization | ||
344 | */ | ||
345 | void | ||
346 | opus_init () | ||
347 | { | ||
348 | int err; | ||
349 | int channels = 1; | ||
350 | sampling_rate = 48000; | ||
351 | frame_size = sampling_rate / 50; | ||
352 | pcm_length = frame_size * channels * sizeof (float); | ||
353 | |||
354 | dec = opus_decoder_create (sampling_rate, channels, &err); | ||
355 | pcm_buffer = (float *) pa_xmalloc (frame_size * channels * sizeof (float)); | ||
356 | } | ||
357 | |||
358 | /** | ||
359 | * The main function for the playback helper. | ||
360 | * | ||
361 | * @param argc number of arguments from the command line | ||
362 | * @param argv command line arguments | ||
363 | * @return 0 ok, 1 on error | ||
364 | */ | ||
365 | int | ||
366 | main (int argc, char *argv[]) | ||
367 | { | ||
368 | char readbuf[MAXLINE]; | ||
369 | struct MessageStreamTokenizer *stdin_mst; | ||
370 | |||
371 | stdin_mst = mst_create (&stdin_receiver, NULL); | ||
372 | |||
373 | opus_init (); | ||
374 | pa_init (); | ||
375 | |||
376 | while (1) | ||
377 | { | ||
378 | ssize_t ret = read (0, readbuf, sizeof (readbuf)); | ||
379 | |||
380 | if (0 > ret) | ||
381 | { | ||
382 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
383 | _("Read error from STDIN: %s\n"), strerror (errno)); | ||
384 | break; | ||
385 | } | ||
386 | |||
387 | mst_receive (stdin_mst, readbuf, ret); | ||
388 | } | ||
389 | |||
390 | return 0; | ||
391 | } | ||