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