-ensure external symbols have proper prefix for conversation service
[oweals/gnunet.git] / src / conversation / gnunet-helper-audio-playback.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 #define MAXLINE 4096
42
43 /**
44 * GNUnet Message Tokenizer
45 */
46 #include "mst.c"
47
48 /**
49 * Pulseaudio specification. May change in the future.
50 */
51 static pa_sample_spec sample_spec = {
52   .format = PA_SAMPLE_FLOAT32LE,
53   .rate = 48000,
54   .channels = 1
55 };
56
57 /**
58 * Pulseaudio mainloop api
59 */
60 static pa_mainloop_api *mainloop_api;
61
62 /**
63 * Pulseaudio threaded mainloop
64 */
65 static pa_threaded_mainloop *m;
66
67 /**
68 * Pulseaudio context
69 */
70 static pa_context *context;
71
72 /**
73 * Pulseaudio output stream
74 */
75 static pa_stream *stream_out;
76
77 /**
78 * Pulseaudio io events
79 */
80 static pa_io_event *stdio_event;
81
82 /**
83 * OPUS decoder
84 */
85 static OpusDecoder *dec;
86
87 /**
88 * PCM data buffer
89 */
90 static float *pcm_buffer;
91
92 /**
93 * Length of PCM buffer
94 */
95 static int pcm_length;
96
97 /**
98 * Number of samples for one frame
99 */
100 static int frame_size;
101
102 /**
103 * The sampling rate used in Pulseaudio specification
104 */
105 static opus_int32 sampling_rate;
106
107 /**
108 * Audio buffer
109 */
110 static void *buffer;
111
112 /**
113 * Length of audio buffer
114 */
115 static size_t buffer_length;
116
117 /**
118 * Read index for transmit buffer
119 */
120 static size_t buffer_index;
121
122
123
124 /**
125 * Message callback
126 */
127 static void
128 stdin_receiver (void *cls, const struct GNUNET_MessageHeader *msg)
129 {
130   struct AudioMessage *audio;
131
132   switch (ntohs (msg->type))
133     {
134     case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
135       audio = (struct AudioMessage *) msg;
136
137       int len =
138         opus_decode_float (dec, audio->audio, audio->length, pcm_buffer,
139                            frame_size, 0);
140
141       if (pa_stream_write
142           (stream_out, (uint8_t *) pcm_buffer, pcm_length, NULL, 0,
143            PA_SEEK_RELATIVE) < 0)
144         {
145
146           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
147                       _("pa_stream_write() failed: %s\n"),
148                       pa_strerror (pa_context_errno (context)));
149           return;
150         }
151
152       break;
153
154     default:
155       break;
156     }
157 }
158
159 /**
160 * Pulseaudio shutdown task
161 */
162 static void
163 quit (int ret)
164 {
165   mainloop_api->quit (mainloop_api, ret);
166   exit (ret);
167 }
168
169 /**
170 * Write some data to the stream 
171 */
172 static void
173 do_stream_write (size_t length)
174 {
175   size_t l;
176   GNUNET_assert (length);
177
178   if (!buffer || !buffer_length)
179     {
180       return;
181     }
182
183
184   l = length;
185   if (l > buffer_length)
186     {
187       l = buffer_length;
188
189     }
190
191   if (pa_stream_write
192       (stream_out, (uint8_t *) buffer + buffer_index, l, NULL, 0,
193        PA_SEEK_RELATIVE) < 0)
194     {
195       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
196                   _("pa_stream_write() failed: %s\n"),
197                   pa_strerror (pa_context_errno (context)));
198       quit (1);
199       return;
200     }
201
202   buffer_length -= l;
203   buffer_index += l;
204
205   if (!buffer_length)
206     {
207       pa_xfree (buffer);
208       buffer = NULL;
209       buffer_index = buffer_length = 0;
210     }
211 }
212
213 /**
214 * Callback when data is there for playback
215 */
216 static void
217 stream_write_callback (pa_stream * s, size_t length, void *userdata)
218 {
219
220   if (stdio_event)
221     {
222       mainloop_api->io_enable (stdio_event, PA_IO_EVENT_INPUT);
223     }
224
225
226   if (!buffer)
227     {
228       return;
229     }
230
231
232   do_stream_write (length);
233 }
234
235 /**
236 * Exit callback for SIGTERM and SIGINT
237 */
238 static void
239 exit_signal_callback (pa_mainloop_api * m, pa_signal_event * e, int sig,
240                       void *userdata)
241 {
242   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
243               _("gnunet-helper-audio-playback - Got signal, exiting\n"));
244   quit (1);
245 }
246
247 /**
248 * Pulseaudio stream state callback
249 */
250 static void
251 context_state_callback (pa_context * c, void *userdata)
252 {
253   int p;
254   GNUNET_assert (c);
255
256   switch (pa_context_get_state (c))
257     {
258     case PA_CONTEXT_CONNECTING:
259     case PA_CONTEXT_AUTHORIZING:
260     case PA_CONTEXT_SETTING_NAME:
261       break;
262
263     case PA_CONTEXT_READY:
264       {
265         GNUNET_assert (c);
266         GNUNET_assert (!stream_out);
267
268         GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Connection established.\n"));
269
270
271         if (!
272             (stream_out =
273              pa_stream_new (c, "GNUNET VoIP playback", &sample_spec, NULL)))
274           {
275             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
276                         _("pa_stream_new() failed: %s\n"),
277                         pa_strerror (pa_context_errno (c)));
278             goto fail;
279           }
280
281         pa_stream_set_write_callback (stream_out, stream_write_callback,
282                                       NULL);
283
284         if ((p =
285              pa_stream_connect_playback (stream_out, NULL, NULL, 0, NULL,
286                                          NULL)) < 0)
287           {
288             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
289                         _("pa_stream_connect_playback() failed: %s\n"),
290                         pa_strerror (pa_context_errno (c)));
291             goto fail;
292           }
293
294         break;
295       }
296
297     case PA_CONTEXT_TERMINATED:
298       quit (0);
299       break;
300
301     case PA_CONTEXT_FAILED:
302     default:
303       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Connection failure: %s\n"),
304                   pa_strerror (pa_context_errno (c)));
305       goto fail;
306     }
307
308   return;
309
310 fail:
311   quit (1);
312
313 }
314
315 /**
316 * Pulseaudio initialization
317 */
318 void
319 pa_init ()
320 {
321   int r;
322
323   if (!pa_sample_spec_valid (&sample_spec))
324     {
325       GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Wrong Spec\n"));
326     }
327
328   /* set up threaded playback mainloop */
329
330   if (!(m = pa_threaded_mainloop_new ()))
331     {
332       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_new() failed.\n"));
333     }
334
335   mainloop_api = pa_threaded_mainloop_get_api (m);
336
337
338   /* listen to signals */
339
340   r = pa_signal_init (mainloop_api);
341   GNUNET_assert (r == 0);
342   pa_signal_new (SIGINT, exit_signal_callback, NULL);
343   pa_signal_new (SIGTERM, exit_signal_callback, NULL);
344
345
346   /* connect to the main pulseaudio context */
347
348   if (!(context = pa_context_new (mainloop_api, "GNUnet VoIP")))
349     {
350       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_context_new() failed.\n"));
351     }
352
353   pa_context_set_state_callback (context, context_state_callback, NULL);
354
355   if (pa_context_connect (context, NULL, 0, NULL) < 0)
356     {
357       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
358                   _("pa_context_connect() failed: %s\n"),
359                   pa_strerror (pa_context_errno (context)));
360     }
361
362   if (pa_threaded_mainloop_start (m) < 0)
363     {
364       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_run() failed.\n"));
365     }
366 }
367
368 /**
369 * OPUS initialization
370 */
371 void
372 opus_init ()
373 {
374   int err;
375   int channels = 1;
376   sampling_rate = 48000;
377   frame_size = sampling_rate / 50;
378   pcm_length = frame_size * channels * sizeof (float);
379
380   dec = opus_decoder_create (sampling_rate, channels, &err);
381   pcm_buffer = (float *) pa_xmalloc (frame_size * channels * sizeof (float));
382 }
383
384 /**
385  * The main function for the playback helper.
386  *
387  * @param argc number of arguments from the command line
388  * @param argv command line arguments
389  * @return 0 ok, 1 on error
390  */
391 int
392 main (int argc, char *argv[])
393 {
394   char readbuf[MAXLINE];
395   struct MessageStreamTokenizer *stdin_mst;
396
397   stdin_mst = mst_create (&stdin_receiver, NULL);
398
399   opus_init ();
400   pa_init ();
401
402   while (1)
403     {
404       ssize_t ret = read (0, readbuf, sizeof (readbuf));
405
406       if (0 > ret)
407         {
408           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
409                       _("Read error from STDIN: %s\n"), strerror (errno));
410           break;
411         }
412
413       mst_receive (stdin_mst, readbuf, ret);
414     }
415   mst_destroy (stdin_mst);
416
417   return 0;
418 }