test + documentation
[oweals/gnunet.git] / src / conversation / gnunet-helper-audio-record-gst.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-record-gst.c
22  * @brief program to record audio data from the microphone (GStreamer version)
23  * @author LRN
24  */
25 #include "platform.h"
26 #include "gnunet_util_lib.h"
27 #include "gnunet_protocols.h"
28 #include "conversation.h"
29 #include "gnunet_constants.h"
30 #include "gnunet_core_service.h"
31
32 #include <gst/gst.h>
33 #include <gst/app/gstappsink.h>
34 #include <gst/audio/gstaudiobasesrc.h>
35 #include <glib.h>
36
37 /**
38  * Number of channels.
39  * Must be one of the following (from libopusenc documentation):
40  * 1, 2
41  */
42 #define OPUS_CHANNELS 1
43
44 /**
45  * Maximal size of a single opus packet.
46  */
47 #define MAX_PAYLOAD_SIZE (1024 / OPUS_CHANNELS)
48
49 /**
50  * Size of a single frame fed to the encoder, in ms.
51  * Must be one of the following (from libopus documentation):
52  * 2.5, 5, 10, 20, 40 or 60
53  */
54 #define OPUS_FRAME_SIZE 20
55
56 /**
57  * Expected packet loss to prepare for, in percents.
58  */
59 #define PACKET_LOSS_PERCENTAGE 1
60
61 /**
62  * Set to 1 to enable forward error correction.
63  * Set to 0 to disable.
64  */
65 #define INBAND_FEC_MODE 1
66
67 /**
68  * Max number of microseconds to buffer in audiosource.
69  * Default is 200000
70  */
71 #define BUFFER_TIME 1000
72
73 /**
74  * Min number of microseconds to buffer in audiosource.
75  * Default is 10000
76  */
77 #define LATENCY_TIME 1000
78
79 /**
80  * Main pipeline.
81  */
82 static GstElement *pipeline;
83
84 static void
85 quit ()
86 {
87   if (NULL != pipeline)
88     gst_element_set_state (pipeline, GST_STATE_NULL);
89 }
90
91 static gboolean
92 bus_call (GstBus *bus, GstMessage *msg, gpointer data)
93 {
94   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bus message\n");
95   switch (GST_MESSAGE_TYPE (msg))
96   {
97   case GST_MESSAGE_EOS:
98     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of stream\n");
99     quit ();
100     break;
101
102   case GST_MESSAGE_ERROR:
103     {
104       gchar  *debug;
105       GError *error;
106       
107       gst_message_parse_error (msg, &error, &debug);
108       g_free (debug);
109       
110       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: %s\n", error->message);
111       g_error_free (error);
112       
113       quit ();
114       break;
115     }
116   default:
117     break;
118   }
119
120   return TRUE;
121 }
122
123 void
124 source_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name, gpointer user_data)
125 {
126   if (GST_IS_AUDIO_BASE_SRC (object))
127     g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time", (gint64) LATENCY_TIME, NULL);
128 }
129
130 static void
131 signalhandler (int s)
132 {
133   quit ();
134 }
135
136
137 int
138 main (int argc, char **argv)
139 {
140   GstElement *source, *encoder, *conv, *resampler, *sink;
141   GstBus *bus;
142   guint bus_watch_id;
143   struct AudioMessage audio_message;
144   int abort_send = 0;
145
146   typedef void (*SignalHandlerPointer) (int);
147  
148   SignalHandlerPointer inthandler, termhandler;
149   inthandler = signal (SIGINT, signalhandler);
150   termhandler = signal (SIGTERM, signalhandler);
151
152 #ifdef WINDOWS
153   setmode (1, _O_BINARY);
154 #endif
155
156   /* Initialisation */
157   gst_init (&argc, &argv);
158
159   GNUNET_assert (GNUNET_OK ==
160                  GNUNET_log_setup ("gnunet-helper-audio-record",
161                                    "WARNING",
162                                    NULL));
163
164   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
165               "Audio source starts\n");
166
167   audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
168
169   /* Create gstreamer elements */
170   pipeline = gst_pipeline_new ("audio-recorder");
171   source   = gst_element_factory_make ("autoaudiosrc",  "audiosource");
172   conv     = gst_element_factory_make ("audioconvert",  "converter");
173   resampler= gst_element_factory_make ("audioresample", "resampler");
174   encoder  = gst_element_factory_make ("opusenc",       "opus-encoder");
175   sink     = gst_element_factory_make ("appsink",       "audio-output");
176
177   if (!pipeline || !source || !conv || !resampler || !encoder || !sink)
178   {
179     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
180         "One element could not be created. Exiting.\n");
181     return -1;
182   }
183
184   g_signal_connect (source, "child-added", G_CALLBACK (source_child_added), NULL);
185
186   /* Set up the pipeline */
187
188   g_object_set (G_OBJECT (encoder),
189 /*      "bitrate", 64000, */
190 /*      "bandwidth", OPUS_BANDWIDTH_FULLBAND, */
191       "inband-fec", INBAND_FEC_MODE,
192       "packet-loss-percentage", PACKET_LOSS_PERCENTAGE,
193       "max-payload-size", MAX_PAYLOAD_SIZE,
194       "audio", FALSE, /* VoIP, not audio */
195       "frame-size", OPUS_FRAME_SIZE,
196       NULL);
197   
198   /* we add a message handler */
199   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
200   bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline);
201   gst_object_unref (bus);
202
203   /* we add all elements into the pipeline */
204   /* audiosource | converter | resampler | opus-encoder | audio-output */
205   gst_bin_add_many (GST_BIN (pipeline), source, conv, resampler, encoder,
206       sink, NULL);
207
208   /* we link the elements together */
209   gst_element_link_many (source, conv, resampler, encoder, sink, NULL);
210
211   /* Set the pipeline to "playing" state*/
212   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n");
213   gst_element_set_state (pipeline, GST_STATE_PLAYING);
214
215
216   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n");
217   /* Iterate */
218   while (!abort_send)
219   {
220     GstSample *s;
221     GstBuffer *b;
222     GstMapInfo m;
223     size_t len, msg_size;
224     const char *ptr;
225     int phase;
226
227     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulling...\n");
228     s = gst_app_sink_pull_sample (GST_APP_SINK (sink));
229     if (NULL == s)
230     {
231       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulled NULL\n");
232       break;
233     }
234     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "...pulled!\n");
235     {
236       const GstStructure *si;
237       char *si_str;
238       GstCaps *s_caps;
239       char *caps_str;
240       si = gst_sample_get_info (s);
241       if (si)
242       {
243         si_str = gst_structure_to_string (si);
244         if (si_str)
245         {
246           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample %s\n", si_str);
247           g_free (si_str);
248         }
249       }
250       else
251       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no info\n");
252       s_caps = gst_sample_get_caps (s);
253       if (s_caps)
254       {
255         caps_str = gst_caps_to_string (s_caps);
256         if (caps_str)
257         {
258           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with caps %s\n", caps_str);
259           g_free (caps_str);
260         }
261       }
262       else
263         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no caps\n");
264     }
265     b = gst_sample_get_buffer (s);
266     if (NULL == b || !gst_buffer_map (b, &m, GST_MAP_READ))
267     {
268       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "got NULL buffer %p or failed to map the buffer\n", b);
269       gst_sample_unref (s);
270       continue;
271     }
272
273     len = m.size;
274     if (len > UINT16_MAX - sizeof (struct AudioMessage))
275     {
276       GNUNET_break (0);
277       len = UINT16_MAX - sizeof (struct AudioMessage);
278     }
279     msg_size = sizeof (struct AudioMessage) + len;
280     audio_message.header.size = htons ((uint16_t) msg_size);
281
282     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
283         "Sending %u bytes of audio data\n", (unsigned int) msg_size);
284     for (phase = 0; phase < 2; phase++)
285     {
286       size_t offset;
287       size_t to_send;
288       ssize_t ret;
289       if (0 == phase)
290       {
291         ptr = (const char *) &audio_message;
292         to_send = sizeof (audio_message);
293       }
294       else
295       {
296         ptr = (const char *) m.data;
297         to_send = len;
298       }
299       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
300           "Sending %u bytes on phase %d\n", (unsigned int) to_send, phase);
301       for (offset = 0; offset < to_send; offset += ret)
302       {
303         ret = write (1, &ptr[offset], to_send - offset);
304         if (0 >= ret)
305         {
306           if (-1 == ret)
307             GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
308                 "Failed to write %u bytes at offset %u (total %u) in phase %d: %s\n",
309                 (unsigned int) to_send - offset, (unsigned int) offset,
310                 (unsigned int) (to_send + offset), phase, strerror (errno));
311           abort_send = 1;
312           break;
313         }
314       }
315       if (abort_send)
316         break;
317     }
318     gst_buffer_unmap (b, &m);
319     gst_sample_unref (s);
320   }
321
322   signal (SIGINT, inthandler);
323   signal (SIGINT, termhandler);
324
325   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returned, stopping playback\n");
326   quit ();
327
328   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting pipeline\n");
329   gst_object_unref (GST_OBJECT (pipeline));
330   pipeline = NULL;
331   g_source_remove (bus_watch_id);
332
333   return 0;
334 }