aboutsummaryrefslogtreecommitdiff
path: root/src/conversation/gnunet-helper-audio-record.c
diff options
context:
space:
mode:
authorAndreas Fuchs <fuchandr@in.tum.de>2013-10-01 11:34:38 +0000
committerAndreas Fuchs <fuchandr@in.tum.de>2013-10-01 11:34:38 +0000
commit70c7532c4d0c684afc4158984f9a37ae7cf05ba3 (patch)
tree693ef5a8b848db69c6e4b79c2d6f2a583f99db0c /src/conversation/gnunet-helper-audio-record.c
parent408c3426ad378e8eef910ca6fea1144110346b0b (diff)
downloadgnunet-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.c446
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*/
18static pa_sample_spec sample_spec = {
19 .format = PA_SAMPLE_FLOAT32LE,
20 .rate = 48000,
21 .channels = 1
22};
23
24/**
25* Pulseaudio mainloop api
26*/
27static pa_mainloop_api *mainloop_api = NULL;
28
29/**
30* Pulseaudio mainloop
31*/
32static pa_mainloop *m = NULL;
33
34/**
35* Pulseaudio context
36*/
37static pa_context *context = NULL;
38
39/**
40* Pulseaudio recording stream
41*/
42static pa_stream *stream_in = NULL;
43
44/**
45* Pulseaudio io events
46*/
47static pa_io_event *stdio_event = NULL;
48
49/**
50* Message tokenizer
51*/
52struct MessageStreamTokenizer *stdin_mst;
53
54/**
55* OPUS encoder
56*/
57OpusEncoder *enc = NULL;
58
59/**
60*
61*/
62unsigned char *opus_data;
63
64/**
65* PCM data buffer for one OPUS frame
66*/
67float *pcm_buffer;
68
69/**
70 * Length of the pcm data needed for one OPUS frame
71 */
72int pcm_length;
73
74/**
75* Number of samples for one frame
76*/
77int frame_size;
78
79/**
80* Maximum length of opus payload
81*/
82int max_payload_bytes = 1500;
83
84/**
85* Audio buffer
86*/
87static void *transmit_buffer = NULL;
88
89/**
90* Length of audio buffer
91*/
92static size_t transmit_buffer_length = 0;
93
94/**
95* Read index for transmit buffer
96*/
97static size_t transmit_buffer_index = 0;
98
99/**
100* Audio message skeleton
101*/
102struct AudioMessage *audio_message;
103
104
105
106/**
107* Pulseaudio shutdown task
108*/
109static void
110quit (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*/
121static void
122packetizer ()
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*/
171static void
172stream_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*/
215static void
216exit_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*/
226static void
227stream_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*/
289static void
290context_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
349fail:
350 quit (1);
351
352}
353
354/**
355 * Pulsaudio init
356 */
357void
358pa_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 */
409void
410opus_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 */
439int
440main (int argc, char *argv[])
441{
442 opus_init ();
443 pa_init ();
444
445 return 0;
446}