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