-spell param(eter) correctly
[oweals/gnunet.git] / src / conversation / gnunet-helper-audio-record.c
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 }