diff options
Diffstat (limited to 'src/conversation/gnunet-helper-audio-record-gst.c')
-rwxr-xr-x | src/conversation/gnunet-helper-audio-record-gst.c | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/src/conversation/gnunet-helper-audio-record-gst.c b/src/conversation/gnunet-helper-audio-record-gst.c new file mode 100755 index 000000000..8d7a88fab --- /dev/null +++ b/src/conversation/gnunet-helper-audio-record-gst.c | |||
@@ -0,0 +1,334 @@ | |||
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 | } | ||