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-record.c | |
parent | 408c3426ad378e8eef910ca6fea1144110346b0b (diff) | |
download | gnunet-70c7532c4d0c684afc4158984f9a37ae7cf05ba3.tar.gz gnunet-70c7532c4d0c684afc4158984f9a37ae7cf05ba3.zip |
Initial conversation (experimental) commit
Diffstat (limited to 'src/conversation/gnunet-helper-audio-record.c')
-rw-r--r-- | src/conversation/gnunet-helper-audio-record.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/conversation/gnunet-helper-audio-record.c b/src/conversation/gnunet-helper-audio-record.c new file mode 100644 index 000000000..839c2f4d3 --- /dev/null +++ b/src/conversation/gnunet-helper-audio-record.c | |||
@@ -0,0 +1,446 @@ | |||
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 | /** | ||
16 | * Specification for recording. May change in the future to spec negotiation. | ||
17 | */ | ||
18 | static pa_sample_spec sample_spec = { | ||
19 | .format = PA_SAMPLE_FLOAT32LE, | ||
20 | .rate = 48000, | ||
21 | .channels = 1 | ||
22 | }; | ||
23 | |||
24 | /** | ||
25 | * Pulseaudio mainloop api | ||
26 | */ | ||
27 | static pa_mainloop_api *mainloop_api = NULL; | ||
28 | |||
29 | /** | ||
30 | * Pulseaudio mainloop | ||
31 | */ | ||
32 | static pa_mainloop *m = NULL; | ||
33 | |||
34 | /** | ||
35 | * Pulseaudio context | ||
36 | */ | ||
37 | static pa_context *context = NULL; | ||
38 | |||
39 | /** | ||
40 | * Pulseaudio recording stream | ||
41 | */ | ||
42 | static pa_stream *stream_in = NULL; | ||
43 | |||
44 | /** | ||
45 | * Pulseaudio io events | ||
46 | */ | ||
47 | static pa_io_event *stdio_event = NULL; | ||
48 | |||
49 | /** | ||
50 | * Message tokenizer | ||
51 | */ | ||
52 | struct MessageStreamTokenizer *stdin_mst; | ||
53 | |||
54 | /** | ||
55 | * OPUS encoder | ||
56 | */ | ||
57 | OpusEncoder *enc = NULL; | ||
58 | |||
59 | /** | ||
60 | * | ||
61 | */ | ||
62 | unsigned char *opus_data; | ||
63 | |||
64 | /** | ||
65 | * PCM data buffer for one OPUS frame | ||
66 | */ | ||
67 | float *pcm_buffer; | ||
68 | |||
69 | /** | ||
70 | * Length of the pcm data needed for one OPUS frame | ||
71 | */ | ||
72 | int pcm_length; | ||
73 | |||
74 | /** | ||
75 | * Number of samples for one frame | ||
76 | */ | ||
77 | int frame_size; | ||
78 | |||
79 | /** | ||
80 | * Maximum length of opus payload | ||
81 | */ | ||
82 | int max_payload_bytes = 1500; | ||
83 | |||
84 | /** | ||
85 | * Audio buffer | ||
86 | */ | ||
87 | static void *transmit_buffer = NULL; | ||
88 | |||
89 | /** | ||
90 | * Length of audio buffer | ||
91 | */ | ||
92 | static size_t transmit_buffer_length = 0; | ||
93 | |||
94 | /** | ||
95 | * Read index for transmit buffer | ||
96 | */ | ||
97 | static size_t transmit_buffer_index = 0; | ||
98 | |||
99 | /** | ||
100 | * Audio message skeleton | ||
101 | */ | ||
102 | struct AudioMessage *audio_message; | ||
103 | |||
104 | |||
105 | |||
106 | /** | ||
107 | * Pulseaudio shutdown task | ||
108 | */ | ||
109 | static void | ||
110 | quit (int ret) | ||
111 | { | ||
112 | mainloop_api->quit (mainloop_api, ret); | ||
113 | exit (ret); | ||
114 | } | ||
115 | |||
116 | |||
117 | |||
118 | /** | ||
119 | * Creates OPUS packets from PCM data | ||
120 | */ | ||
121 | static void | ||
122 | packetizer () | ||
123 | { | ||
124 | |||
125 | |||
126 | while (transmit_buffer_length >= transmit_buffer_index + pcm_length) | ||
127 | { | ||
128 | |||
129 | int ret; | ||
130 | int len; | ||
131 | |||
132 | size_t msg_size = sizeof (struct AudioMessage); | ||
133 | |||
134 | memcpy (pcm_buffer, | ||
135 | (float *) transmit_buffer + | ||
136 | (transmit_buffer_index / sizeof (float)), pcm_length); | ||
137 | len = | ||
138 | opus_encode_float (enc, pcm_buffer, frame_size, opus_data, | ||
139 | max_payload_bytes); | ||
140 | |||
141 | audio_message->length = len; | ||
142 | memcpy (audio_message->audio, opus_data, len); | ||
143 | |||
144 | if ((ret = write (1, audio_message, msg_size)) != msg_size) | ||
145 | { | ||
146 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("write")); | ||
147 | return; | ||
148 | } | ||
149 | |||
150 | transmit_buffer_index += pcm_length; | ||
151 | } | ||
152 | |||
153 | int new_size = transmit_buffer_length - transmit_buffer_index; | ||
154 | |||
155 | if (0 != new_size) | ||
156 | { | ||
157 | |||
158 | transmit_buffer = pa_xrealloc (transmit_buffer, new_size); | ||
159 | memcpy (transmit_buffer, transmit_buffer + transmit_buffer_index, | ||
160 | new_size); | ||
161 | |||
162 | transmit_buffer_index = 0; | ||
163 | transmit_buffer_length = new_size; | ||
164 | } | ||
165 | |||
166 | } | ||
167 | |||
168 | /** | ||
169 | * Pulseaudio callback when new data is available. | ||
170 | */ | ||
171 | static void | ||
172 | stream_read_callback (pa_stream * s, size_t length, void *userdata) | ||
173 | { | ||
174 | const void *data; | ||
175 | GNUNET_assert (s); | ||
176 | GNUNET_assert (length > 0); | ||
177 | |||
178 | if (stdio_event) | ||
179 | mainloop_api->io_enable (stdio_event, PA_IO_EVENT_OUTPUT); | ||
180 | |||
181 | if (pa_stream_peek (s, (const void **) &data, &length) < 0) | ||
182 | { | ||
183 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_stream_peek() failed: %s\n"), | ||
184 | pa_strerror (pa_context_errno (context))); | ||
185 | quit (1); | ||
186 | return; | ||
187 | } | ||
188 | |||
189 | GNUNET_assert (data); | ||
190 | GNUNET_assert (length > 0); | ||
191 | |||
192 | if (transmit_buffer) | ||
193 | { | ||
194 | transmit_buffer = | ||
195 | pa_xrealloc (transmit_buffer, transmit_buffer_length + length); | ||
196 | memcpy ((uint8_t *) transmit_buffer + transmit_buffer_length, data, | ||
197 | length); | ||
198 | transmit_buffer_length += length; | ||
199 | } | ||
200 | else | ||
201 | { | ||
202 | transmit_buffer = pa_xmalloc (length); | ||
203 | memcpy (transmit_buffer, data, length); | ||
204 | transmit_buffer_length = length; | ||
205 | transmit_buffer_index = 0; | ||
206 | } | ||
207 | |||
208 | pa_stream_drop (s); | ||
209 | packetizer (); | ||
210 | } | ||
211 | |||
212 | /** | ||
213 | * Exit callback for SIGTERM and SIGINT | ||
214 | */ | ||
215 | static void | ||
216 | exit_signal_callback (pa_mainloop_api * m, pa_signal_event * e, int sig, | ||
217 | void *userdata) | ||
218 | { | ||
219 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Got signal, exiting.\n")); | ||
220 | quit (1); | ||
221 | } | ||
222 | |||
223 | /** | ||
224 | * Pulseaudio stream state callback | ||
225 | */ | ||
226 | static void | ||
227 | stream_state_callback (pa_stream * s, void *userdata) | ||
228 | { | ||
229 | GNUNET_assert (s); | ||
230 | |||
231 | switch (pa_stream_get_state (s)) | ||
232 | { | ||
233 | case PA_STREAM_CREATING: | ||
234 | case PA_STREAM_TERMINATED: | ||
235 | break; | ||
236 | |||
237 | case PA_STREAM_READY: | ||
238 | if (1) | ||
239 | { | ||
240 | const pa_buffer_attr *a; | ||
241 | char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], | ||
242 | sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; | ||
243 | |||
244 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
245 | _("Stream successfully created.\n")); | ||
246 | |||
247 | if (!(a = pa_stream_get_buffer_attr (s))) | ||
248 | { | ||
249 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
250 | _("pa_stream_get_buffer_attr() failed: %s\n"), | ||
251 | pa_strerror (pa_context_errno | ||
252 | (pa_stream_get_context (s)))); | ||
253 | |||
254 | } | ||
255 | else | ||
256 | { | ||
257 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
258 | _("Buffer metrics: maxlength=%u, fragsize=%u\n"), | ||
259 | a->maxlength, a->fragsize); | ||
260 | } | ||
261 | |||
262 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
263 | _("Using sample spec '%s', channel map '%s'.\n"), | ||
264 | pa_sample_spec_snprint (sst, sizeof (sst), | ||
265 | pa_stream_get_sample_spec (s)), | ||
266 | pa_channel_map_snprint (cmt, sizeof (cmt), | ||
267 | pa_stream_get_channel_map (s))); | ||
268 | |||
269 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
270 | _("Connected to device %s (%u, %ssuspended).\n"), | ||
271 | pa_stream_get_device_name (s), | ||
272 | pa_stream_get_device_index (s), | ||
273 | pa_stream_is_suspended (s) ? "" : "not "); | ||
274 | } | ||
275 | |||
276 | break; | ||
277 | |||
278 | case PA_STREAM_FAILED: | ||
279 | default: | ||
280 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Stream error: %s\n"), | ||
281 | pa_strerror (pa_context_errno (pa_stream_get_context (s)))); | ||
282 | quit (1); | ||
283 | } | ||
284 | } | ||
285 | |||
286 | /** | ||
287 | * Pulseaudio context state callback | ||
288 | */ | ||
289 | static void | ||
290 | context_state_callback (pa_context * c, void *userdata) | ||
291 | { | ||
292 | GNUNET_assert (c); | ||
293 | |||
294 | switch (pa_context_get_state (c)) | ||
295 | { | ||
296 | case PA_CONTEXT_CONNECTING: | ||
297 | case PA_CONTEXT_AUTHORIZING: | ||
298 | case PA_CONTEXT_SETTING_NAME: | ||
299 | break; | ||
300 | |||
301 | case PA_CONTEXT_READY: | ||
302 | { | ||
303 | int r; | ||
304 | |||
305 | GNUNET_assert (c); | ||
306 | GNUNET_assert (!stream_in); | ||
307 | |||
308 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Connection established.\n")); | ||
309 | |||
310 | if (! | ||
311 | (stream_in = | ||
312 | pa_stream_new (c, "GNUNET_VoIP recorder", &sample_spec, NULL))) | ||
313 | { | ||
314 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
315 | _("pa_stream_new() failed: %s\n"), | ||
316 | pa_strerror (pa_context_errno (c))); | ||
317 | goto fail; | ||
318 | } | ||
319 | |||
320 | |||
321 | pa_stream_set_state_callback (stream_in, stream_state_callback, NULL); | ||
322 | pa_stream_set_read_callback (stream_in, stream_read_callback, NULL); | ||
323 | |||
324 | |||
325 | if ((r = pa_stream_connect_record (stream_in, NULL, NULL, 0)) < 0) | ||
326 | { | ||
327 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
328 | _("pa_stream_connect_record() failed: %s\n"), | ||
329 | pa_strerror (pa_context_errno (c))); | ||
330 | goto fail; | ||
331 | } | ||
332 | |||
333 | break; | ||
334 | } | ||
335 | |||
336 | case PA_CONTEXT_TERMINATED: | ||
337 | quit (0); | ||
338 | break; | ||
339 | |||
340 | case PA_CONTEXT_FAILED: | ||
341 | default: | ||
342 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Connection failure: %s\n"), | ||
343 | pa_strerror (pa_context_errno (c))); | ||
344 | goto fail; | ||
345 | } | ||
346 | |||
347 | return; | ||
348 | |||
349 | fail: | ||
350 | quit (1); | ||
351 | |||
352 | } | ||
353 | |||
354 | /** | ||
355 | * Pulsaudio init | ||
356 | */ | ||
357 | void | ||
358 | pa_init () | ||
359 | { | ||
360 | int r; | ||
361 | int i; | ||
362 | |||
363 | if (!pa_sample_spec_valid (&sample_spec)) | ||
364 | { | ||
365 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Wrong Spec\n")); | ||
366 | } | ||
367 | |||
368 | /* set up main record loop */ | ||
369 | |||
370 | if (!(m = pa_mainloop_new ())) | ||
371 | { | ||
372 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_new() failed.\n")); | ||
373 | } | ||
374 | |||
375 | mainloop_api = pa_mainloop_get_api (m); | ||
376 | |||
377 | /* listen to signals */ | ||
378 | |||
379 | r = pa_signal_init (mainloop_api); | ||
380 | GNUNET_assert (r == 0); | ||
381 | pa_signal_new (SIGINT, exit_signal_callback, NULL); | ||
382 | pa_signal_new (SIGTERM, exit_signal_callback, NULL); | ||
383 | |||
384 | /* connect to the main pulseaudio context */ | ||
385 | |||
386 | if (!(context = pa_context_new (mainloop_api, "GNUNET VoIP"))) | ||
387 | { | ||
388 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_context_new() failed.\n")); | ||
389 | } | ||
390 | |||
391 | pa_context_set_state_callback (context, context_state_callback, NULL); | ||
392 | |||
393 | if (pa_context_connect (context, NULL, 0, NULL) < 0) | ||
394 | { | ||
395 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
396 | _("pa_context_connect() failed: %s\n"), | ||
397 | pa_strerror (pa_context_errno (context))); | ||
398 | } | ||
399 | |||
400 | if (pa_mainloop_run (m, &i) < 0) | ||
401 | { | ||
402 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_run() failed.\n")); | ||
403 | } | ||
404 | } | ||
405 | |||
406 | /** | ||
407 | * OPUS init | ||
408 | */ | ||
409 | void | ||
410 | opus_init () | ||
411 | { | ||
412 | opus_int32 sampling_rate = 48000; | ||
413 | frame_size = sampling_rate / 50; | ||
414 | int channels = 1; | ||
415 | |||
416 | pcm_length = frame_size * channels * sizeof (float); | ||
417 | |||
418 | int err; | ||
419 | |||
420 | enc = | ||
421 | opus_encoder_create (sampling_rate, channels, OPUS_APPLICATION_VOIP, | ||
422 | &err); | ||
423 | pcm_buffer = (float *) pa_xmalloc (pcm_length); | ||
424 | opus_data = (unsigned char *) calloc (max_payload_bytes, sizeof (char)); | ||
425 | |||
426 | audio_message = pa_xmalloc (sizeof (struct AudioMessage)); | ||
427 | |||
428 | audio_message->header.size = htons (sizeof (struct AudioMessage)); | ||
429 | audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); | ||
430 | } | ||
431 | |||
432 | /** | ||
433 | * The main function for the record helper. | ||
434 | * | ||
435 | * @param argc number of arguments from the command line | ||
436 | * @param argv command line arguments | ||
437 | * @return 0 ok, 1 on error | ||
438 | */ | ||
439 | int | ||
440 | main (int argc, char *argv[]) | ||
441 | { | ||
442 | opus_init (); | ||
443 | pa_init (); | ||
444 | |||
445 | return 0; | ||
446 | } | ||