GNUnet  0.10.x
gnunet-helper-audio-record.c
Go to the documentation of this file.
1 /*
2  This file is part of GNUnet.
3  Copyright (C) 2013 GNUnet e.V.
4 
5  GNUnet is free software: you can redistribute it and/or modify it
6  under the terms of the GNU Affero General Public License as published
7  by the Free Software Foundation, either version 3 of the License,
8  or (at your 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  Affero General Public License for more details.
14 
15  You should have received a copy of the GNU Affero General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 
18  SPDX-License-Identifier: AGPL3.0-or-later
19 */
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_protocols.h"
30 #include "conversation.h"
31 #include "gnunet_constants.h"
32 #include "gnunet_core_service.h"
33 
34 #include <pulse/simple.h>
35 #include <pulse/error.h>
36 #include <pulse/rtclock.h>
37 
38 #include <pulse/pulseaudio.h>
39 #include <opus/opus.h>
40 #include <opus/opus_types.h>
41 #include <ogg/ogg.h>
42 
43 #define DEBUG_RECORD_PURE_OGG 1
44 
48 #define SAMPLING_RATE 48000
49 
55 #define FRAME_SIZE_MS 40
56 
60 #define FRAME_SIZE (SAMPLING_RATE / 1000 * FRAME_SIZE_MS)
61 
71 #define PAGE_WATERLINE 800
72 
76 #define MAX_PAYLOAD_BYTES 1024
77 
81 #define CHANNELS 1
82 
90 #define CONV_OPUS_PACKET_LOSS_PERCENTAGE 1
91 
98 #define CONV_OPUS_ENCODING_COMPLEXITY 10
99 
105 #define CONV_OPUS_INBAND_FEC 1
106 
117 #define CONV_OPUS_SIGNAL OPUS_SIGNAL_VOICE
118 
138 #define CONV_OPUS_APP_TYPE OPUS_APPLICATION_VOIP
139 
143 static pa_sample_spec sample_spec = {
144  .format = PA_SAMPLE_FLOAT32LE,
145  .rate = SAMPLING_RATE,
146  .channels = CHANNELS
147 };
148 
150 
151 /* OggOpus spec says the numbers must be in little-endian order */
152 struct OpusHeadPacket
153 {
154  uint8_t magic[8];
155  uint8_t version;
156  uint8_t channels;
157  uint16_t preskip GNUNET_PACKED;
158  uint32_t sampling_rate GNUNET_PACKED;
159  uint16_t gain GNUNET_PACKED;
160  uint8_t channel_mapping;
161 };
162 
164 {
165  uint8_t magic[8];
166  uint32_t vendor_length;
167  /* followed by:
168  char vendor[vendor_length];
169  uint32_t string_count;
170  followed by @a string_count pairs of:
171  uint32_t string_length;
172  char string[string_length];
173  */
174 };
175 
177 
181 static pa_mainloop_api *mainloop_api;
182 
186 static pa_mainloop *m;
187 
191 static pa_context *context;
192 
196 static pa_stream *stream_in;
197 
201 static pa_io_event *stdio_event;
202 
206 static OpusEncoder *enc;
207 
211 static unsigned char *opus_data;
212 
216 static float *pcm_buffer;
217 
221 static int pcm_length;
222 
226 static char *transmit_buffer;
227 
232 
236 static size_t transmit_buffer_index;
237 
242 
246 static ogg_stream_state os;
247 
251 static int32_t packet_id;
252 
256 static int64_t enc_granulepos;
257 
258 #ifdef DEBUG_RECORD_PURE_OGG
259 
263 static int dump_pure_ogg;
264 #endif
265 
269 static void
270 quit (int ret)
271 {
272  mainloop_api->quit (mainloop_api,
273  ret);
274  exit (ret);
275 }
276 
277 
278 static void
279 write_data (const char *ptr,
280  size_t msg_size)
281 {
282  ssize_t ret;
283  size_t off;
284  off = 0;
285  while (off < msg_size)
286  {
287  ret = write (STDOUT_FILENO,
288  &ptr[off],
289  msg_size - off);
290  if (0 >= ret)
291  {
292  if (-1 == ret)
294  "write");
295  quit (2);
296  }
297  off += ret;
298  }
299 }
300 
301 
302 static void
303 write_page (ogg_page *og)
304 {
305  static unsigned long long toff;
306  size_t msg_size;
307  msg_size = sizeof (struct AudioMessage) + og->header_len + og->body_len;
308  audio_message->header.size = htons ((uint16_t) msg_size);
309  GNUNET_memcpy (&audio_message[1], og->header, og->header_len);
310  GNUNET_memcpy (((char *) &audio_message[1]) + og->header_len, og->body, og->body_len);
311 
312  toff += msg_size;
314  "Sending %u bytes of audio data (total: %llu)\n",
315  (unsigned int) msg_size,
316  toff);
317 #ifdef DEBUG_RECORD_PURE_OGG
318  if (dump_pure_ogg)
319  write_data ((const char *) &audio_message[1],
320  og->header_len + og->body_len);
321  else
322 #endif
323  write_data ((const char *) audio_message,
324  msg_size);
325 }
326 
327 
331 static void
333 {
334  char *nbuf;
335  size_t new_size;
336  int32_t len;
337  ogg_packet op;
338  ogg_page og;
339 
341  {
344  pcm_length);
345  transmit_buffer_index += pcm_length;
346  len =
347  opus_encode_float (enc, pcm_buffer, FRAME_SIZE, opus_data,
349 
350  if (len < 0)
351  {
353  _("opus_encode_float() failed: %s. Aborting\n"),
354  opus_strerror (len));
355  quit (5);
356  }
357  if (((uint32_t)len) > UINT16_MAX - sizeof (struct AudioMessage))
358  {
359  GNUNET_break (0);
360  continue;
361  }
362 
363  /* As per OggOpus spec, granule is calculated as if the audio
364  had 48kHz sampling rate. */
366 
367  op.packet = (unsigned char *) opus_data;
368  op.bytes = len;
369  op.b_o_s = 0;
370  op.e_o_s = 0;
371  op.granulepos = enc_granulepos;
372  op.packetno = packet_id++;
373  ogg_stream_packetin (&os, &op);
374 
375  while (ogg_stream_flush_fill (&os, &og, PAGE_WATERLINE))
376  {
377  if ( ((unsigned long long) og.header_len) +
378  ((unsigned long long) og.body_len) >
379  UINT16_MAX - sizeof (struct AudioMessage))
380  {
381  GNUNET_assert (0);
382  continue;
383  }
384  write_page (&og);
385  }
386  }
387 
389  if (0 != new_size)
390  {
391  nbuf = pa_xmalloc (new_size);
392  memmove (nbuf,
393  &transmit_buffer[transmit_buffer_index],
394  new_size);
395  pa_xfree (transmit_buffer);
396  transmit_buffer = nbuf;
397  }
398  else
399  {
400  pa_xfree (transmit_buffer);
401  transmit_buffer = NULL;
402  }
403  transmit_buffer_index = 0;
404  transmit_buffer_length = new_size;
405 }
406 
407 
411 static void
412 stream_read_callback (pa_stream * s,
413  size_t length,
414  void *userdata)
415 {
416  const void *data;
417 
418  (void) userdata;
420  "Got %u/%d bytes of PCM data\n",
421  (unsigned int) length,
422  pcm_length);
423 
424  GNUNET_assert (NULL != s);
425  GNUNET_assert (length > 0);
426  if (stdio_event)
427  mainloop_api->io_enable (stdio_event, PA_IO_EVENT_OUTPUT);
428 
429  if (pa_stream_peek (s, (const void **) &data, &length) < 0)
430  {
432  _("pa_stream_peek() failed: %s\n"),
433  pa_strerror (pa_context_errno (context)));
434  quit (1);
435  return;
436  }
437  GNUNET_assert (NULL != data);
438  GNUNET_assert (length > 0);
439  if (NULL != transmit_buffer)
440  {
441  transmit_buffer = pa_xrealloc (transmit_buffer,
442  transmit_buffer_length + length);
444  data,
445  length);
446  transmit_buffer_length += length;
447  }
448  else
449  {
450  transmit_buffer = pa_xmalloc (length);
451  GNUNET_memcpy (transmit_buffer, data, length);
452  transmit_buffer_length = length;
454  }
455  pa_stream_drop (s);
456  packetizer ();
457 }
458 
459 
463 static void
464 exit_signal_callback (pa_mainloop_api * m,
465  pa_signal_event * e,
466  int sig,
467  void *userdata)
468 {
469  (void) m;
470  (void) e;
471  (void) sig;
472  (void) userdata;
474  _("Got signal, exiting.\n"));
475  quit (1);
476 }
477 
478 
482 static void
483 stream_state_callback (pa_stream * s,
484  void *userdata)
485 {
486  (void) userdata;
487  GNUNET_assert (NULL != s);
488  switch (pa_stream_get_state (s))
489  {
490  case PA_STREAM_CREATING:
491  case PA_STREAM_TERMINATED:
492  break;
493  case PA_STREAM_READY:
494  {
495  const pa_buffer_attr *a;
496  char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
497  char sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
498 
500  _("Stream successfully created.\n"));
501 
502  if (!(a = pa_stream_get_buffer_attr (s)))
503  {
505  _("pa_stream_get_buffer_attr() failed: %s\n"),
506  pa_strerror (pa_context_errno
507  (pa_stream_get_context (s))));
508 
509  }
510  else
511  {
513  _("Buffer metrics: maxlength=%u, fragsize=%u\n"),
514  a->maxlength, a->fragsize);
515  }
517  _("Using sample spec '%s', channel map '%s'.\n"),
518  pa_sample_spec_snprint (sst, sizeof (sst),
519  pa_stream_get_sample_spec (s)),
520  pa_channel_map_snprint (cmt, sizeof (cmt),
521  pa_stream_get_channel_map (s)));
522 
524  _("Connected to device %s (%u, %ssuspended).\n"),
525  pa_stream_get_device_name (s),
526  pa_stream_get_device_index (s),
527  pa_stream_is_suspended (s) ? "" : "not ");
528  }
529  break;
530  case PA_STREAM_FAILED:
531  default:
533  _("Stream error: %s\n"),
534  pa_strerror (pa_context_errno (pa_stream_get_context (s))));
535  quit (1);
536  }
537 }
538 
539 
543 static void
544 context_state_callback (pa_context * c,
545  void *userdata)
546 {
547  (void) userdata;
548  GNUNET_assert (c);
549 
550  switch (pa_context_get_state (c))
551  {
552  case PA_CONTEXT_CONNECTING:
553  case PA_CONTEXT_AUTHORIZING:
554  case PA_CONTEXT_SETTING_NAME:
555  break;
556  case PA_CONTEXT_READY:
557  {
558  int r;
559  pa_buffer_attr na;
560 
563  _("Connection established.\n"));
564  if (! (stream_in =
565  pa_stream_new (c, "GNUNET_VoIP recorder", &sample_spec, NULL)))
566  {
568  _("pa_stream_new() failed: %s\n"),
569  pa_strerror (pa_context_errno (c)));
570  goto fail;
571  }
572  pa_stream_set_state_callback (stream_in, &stream_state_callback, NULL);
573  pa_stream_set_read_callback (stream_in, &stream_read_callback, NULL);
574  memset (&na, 0, sizeof (na));
575  na.maxlength = UINT32_MAX;
576  na.fragsize = pcm_length;
577  if ((r = pa_stream_connect_record (stream_in, NULL, &na,
578  PA_STREAM_ADJUST_LATENCY)) < 0)
579  {
581  _("pa_stream_connect_record() failed: %s\n"),
582  pa_strerror (pa_context_errno (c)));
583  goto fail;
584  }
585 
586  break;
587  }
588  case PA_CONTEXT_TERMINATED:
589  quit (0);
590  break;
591  case PA_CONTEXT_FAILED:
592  default:
594  _("Connection failure: %s\n"),
595  pa_strerror (pa_context_errno (c)));
596  goto fail;
597  }
598  return;
599 
600 fail:
601  quit (1);
602 }
603 
604 
608 static void
610 {
611  int r;
612  int i;
613 
614  if (!pa_sample_spec_valid (&sample_spec))
615  {
617  _("Wrong Spec\n"));
618  }
619  /* set up main record loop */
620  if (!(m = pa_mainloop_new ()))
621  {
623  _("pa_mainloop_new() failed.\n"));
624  }
625  mainloop_api = pa_mainloop_get_api (m);
626 
627  /* listen to signals */
628  r = pa_signal_init (mainloop_api);
629  GNUNET_assert (r == 0);
630  pa_signal_new (SIGINT, &exit_signal_callback, NULL);
631  pa_signal_new (SIGTERM, &exit_signal_callback, NULL);
632 
633  /* connect to the main pulseaudio context */
634 
635  if (!(context = pa_context_new (mainloop_api, "GNUNET VoIP")))
636  {
638  _("pa_context_new() failed.\n"));
639  }
640  pa_context_set_state_callback (context, &context_state_callback, NULL);
641  if (pa_context_connect (context, NULL, 0, NULL) < 0)
642  {
644  _("pa_context_connect() failed: %s\n"),
645  pa_strerror (pa_context_errno (context)));
646  }
647  if (pa_mainloop_run (m, &i) < 0)
648  {
650  _("pa_mainloop_run() failed.\n"));
651  }
652 }
653 
654 
658 static void
660 {
661  int err;
662 
663  pcm_length = FRAME_SIZE * CHANNELS * sizeof (float);
664  pcm_buffer = pa_xmalloc (pcm_length);
666  enc = opus_encoder_create (SAMPLING_RATE,
667  CHANNELS,
669  &err);
670  opus_encoder_ctl (enc,
671  OPUS_SET_PACKET_LOSS_PERC (CONV_OPUS_PACKET_LOSS_PERCENTAGE));
672  opus_encoder_ctl (enc,
673  OPUS_SET_COMPLEXITY (CONV_OPUS_ENCODING_COMPLEXITY));
674  opus_encoder_ctl (enc,
675  OPUS_SET_INBAND_FEC (CONV_OPUS_INBAND_FEC));
676  opus_encoder_ctl (enc,
677  OPUS_SET_SIGNAL (CONV_OPUS_SIGNAL));
678 }
679 
680 
681 static void
683 {
684  int serialno;
685  struct OpusHeadPacket headpacket;
686  struct OpusCommentsPacket *commentspacket;
687  size_t commentspacket_len;
688 
690  0x7FFFFFFF);
691  /*Initialize Ogg stream struct*/
692  if (-1 == ogg_stream_init (&os, serialno))
693  {
695  _("ogg_stream_init() failed.\n"));
696  exit (3);
697  }
698 
699  packet_id = 0;
700 
701  /*Write header*/
702  {
703  ogg_packet op;
704  ogg_page og;
705  const char *opusver;
706  int vendor_length;
707 
708  GNUNET_memcpy (headpacket.magic, "OpusHead", 8);
709  headpacket.version = 1;
710  headpacket.channels = CHANNELS;
711  headpacket.preskip = GNUNET_htole16 (0);
713  headpacket.gain = GNUNET_htole16 (0);
714  headpacket.channel_mapping = 0; /* Mono or stereo */
715 
716  op.packet = (unsigned char *) &headpacket;
717  op.bytes = sizeof (headpacket);
718  op.b_o_s = 1;
719  op.e_o_s = 0;
720  op.granulepos = 0;
721  op.packetno = packet_id++;
722  ogg_stream_packetin (&os, &op);
723 
724  /* Head packet must be alone on its page */
725  while (ogg_stream_flush (&os, &og))
726  {
727  write_page (&og);
728  }
729 
730  commentspacket_len = sizeof (*commentspacket);
731  opusver = opus_get_version_string ();
732  vendor_length = strlen (opusver);
733  commentspacket_len += vendor_length;
734  commentspacket_len += sizeof (uint32_t);
735 
736  commentspacket = (struct OpusCommentsPacket *) malloc (commentspacket_len);
737  if (NULL == commentspacket)
738  {
740  _("Failed to allocate %u bytes for second packet\n"),
741  (unsigned int) commentspacket_len);
742  exit (5);
743  }
744 
745  GNUNET_memcpy (commentspacket->magic, "OpusTags", 8);
746  commentspacket->vendor_length = GNUNET_htole32 (vendor_length);
747  GNUNET_memcpy (&commentspacket[1], opusver, vendor_length);
748  *(uint32_t *) &((char *) &commentspacket[1])[vendor_length] = \
749  GNUNET_htole32 (0); /* no tags */
750 
751  op.packet = (unsigned char *) commentspacket;
752  op.bytes = commentspacket_len;
753  op.b_o_s = 0;
754  op.e_o_s = 0;
755  op.granulepos = 0;
756  op.packetno = packet_id++;
757  ogg_stream_packetin (&os, &op);
758 
759  /* Comment packets must not be mixed with audio packets on their pages */
760  while (ogg_stream_flush (&os, &og))
761  {
762  write_page (&og);
763  }
764 
765  free (commentspacket);
766  }
767 }
768 
776 int
777 main (int argc,
778  char *argv[])
779 {
780  (void) argc;
781  (void) argv;
783  GNUNET_log_setup ("gnunet-helper-audio-record",
784  "WARNING",
785  NULL));
787  "Audio source starts\n");
788  audio_message = GNUNET_malloc (UINT16_MAX);
789  audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
790 
791 #ifdef DEBUG_RECORD_PURE_OGG
792  dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0;
793 #endif
794  ogg_init ();
795  opus_init ();
796  pa_init ();
797  return 0;
798 }
static void ogg_init()
static void write_page(ogg_page *og)
#define CONV_OPUS_ENCODING_COMPLEXITY
Configures the encoder&#39;s computational complexity.
static size_t transmit_buffer_index
Read index for transmit buffer.
struct GNUNET_MessageHeader header
Type is GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO.
Definition: conversation.h:60
static int pcm_length
Length of the pcm data needed for one OPUS frame.
static void write_data(const char *ptr, size_t msg_size)
static int64_t enc_granulepos
Ogg granule for current packet.
constants for network protocols
static GNUNET_NETWORK_STRUCT_END pa_mainloop_api * mainloop_api
Pulseaudio mainloop api.
uint32_t GNUNET_CRYPTO_random_u32(enum GNUNET_CRYPTO_Quality mode, uint32_t i)
Produce a random value.
#define GNUNET_assert(cond)
Use this for fatal errors that cannot be handled.
static pa_context * context
Pulseaudio context.
static pa_stream * stream_in
Pulseaudio recording stream.
#define CONV_OPUS_SIGNAL
Configures the type of signal being encoded.
static struct Experiment * e
#define GNUNET_OK
Named constants for return values.
Definition: gnunet_common.h:78
uint16_t size
The length of the struct (in bytes, including the length field itself), in big-endian format...
static int ret
Final status code.
Definition: gnunet-arm.c:89
#define GNUNET_break(cond)
Use this for internal assertion violations that are not fatal (can be handled) but should not occur...
#define GNUNET_NETWORK_STRUCT_BEGIN
Define as empty, GNUNET_PACKED should suffice, but this won&#39;t work on W32.
#define _(String)
GNU gettext support macro.
Definition: platform.h:208
static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata)
Exit callback for SIGTERM and SIGINT.
static void context_state_callback(pa_context *c, void *userdata)
Pulseaudio context state callback.
#define CONV_OPUS_INBAND_FEC
Configures the encoder&#39;s use of inband forward error correction (FEC).
#define CHANNELS
Number of channels.
#define GNUNET_log_strerror(level, cmd)
Log an error message at log-level &#39;level&#39; that indicates a failure of the command &#39;cmd&#39; with the mess...
uint16_t type
The type of the message (GNUNET_MESSAGE_TYPE_XXXX), in big-endian format.
#define GNUNET_memcpy(dst, src, n)
static void opus_init()
OPUS init.
Message to transmit the audio (between client and helpers).
Definition: conversation.h:55
#define SAMPLING_RATE
Sampling rate.
#define GNUNET_htole32(x)
#define CONV_OPUS_PACKET_LOSS_PERCENTAGE
Configures the encoder&#39;s expected packet loss percentage.
static void pa_init()
Pulsaudio init.
static char * transmit_buffer
Audio buffer.
static void quit(int ret)
Pulseaudio shutdown task.
static void stream_state_callback(pa_stream *s, void *userdata)
Pulseaudio stream state callback.
#define GNUNET_htole16(x)
static struct AudioMessage * audio_message
Audio message skeleton.
char * getenv()
static pa_mainloop * m
Pulseaudio mainloop.
#define MAX_PAYLOAD_BYTES
Maximum length of opus payload.
static size_t transmit_buffer_length
Length of audio buffer.
static int dump_pure_ogg
1 to not to write GNUnet message headers, producing pure playable ogg output
static void packetizer()
Creates OPUS packets from PCM data.
#define GNUNET_NETWORK_STRUCT_END
Define as empty, GNUNET_PACKED should suffice, but this won&#39;t work on W32;.
#define GNUNET_PACKED
gcc-ism to get packed structs.
static unsigned char * opus_data
Buffer for encoded data.
static OpusEncoder * enc
OPUS encoder.
static pa_io_event * stdio_event
Pulseaudio io events.
#define GNUNET_log(kind,...)
#define FRAME_SIZE
How many samples to buffer before encoding them.
static pa_sample_spec sample_spec
Specification for recording.
#define GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO
Message to transmit the audio between helper and speaker/microphone library.
static int32_t packet_id
Ogg packet id.
static void stream_read_callback(pa_stream *s, size_t length, void *userdata)
Pulseaudio callback when new data is available.
int GNUNET_log_setup(const char *comp, const char *loglevel, const char *logfile)
Setup logging.
static struct GNUNET_ARM_Operation * op
Current operation.
Definition: gnunet-arm.c:139
static float * pcm_buffer
PCM data buffer for one OPUS frame.
static ogg_stream_state os
Ogg muxer state.
uint32_t data
The data value.
#define CONV_OPUS_APP_TYPE
Coding mode.
int main(int argc, char *argv[])
The main function for the record helper.
#define PAGE_WATERLINE
Pages are commited when their size goes over this value.
#define GNUNET_malloc(size)
Wrapper around malloc.
uint16_t len
length of data (which is always a uint32_t, but presumably this can be used to specify that fewer byt...
High-quality operations are desired.