GNUnet  0.19.4
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
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 
285  off = 0;
286  while (off < msg_size)
287  {
288  ret = write (STDOUT_FILENO,
289  &ptr[off],
290  msg_size - off);
291  if (0 >= ret)
292  {
293  if (-1 == ret)
295  "write");
296  quit (2);
297  }
298  off += ret;
299  }
300 }
301 
302 
303 static void
304 write_page (ogg_page *og)
305 {
306  static unsigned long long toff;
307  size_t msg_size;
308 
309  msg_size = sizeof(struct AudioMessage) + og->header_len + og->body_len;
310  audio_message->header.size = htons ((uint16_t) msg_size);
311  GNUNET_memcpy (&audio_message[1], og->header, og->header_len);
312  GNUNET_memcpy (((char *) &audio_message[1]) + og->header_len, og->body,
313  og->body_len);
314 
315  toff += msg_size;
317  "Sending %u bytes of audio data (total: %llu)\n",
318  (unsigned int) msg_size,
319  toff);
320 #ifdef DEBUG_RECORD_PURE_OGG
321  if (dump_pure_ogg)
322  write_data ((const char *) &audio_message[1],
323  og->header_len + og->body_len);
324  else
325 #endif
326  write_data ((const char *) audio_message,
327  msg_size);
328 }
329 
330 
334 static void
336 {
337  char *nbuf;
338  size_t new_size;
339  int32_t len;
340  ogg_packet op;
341  ogg_page og;
342 
344  {
347  pcm_length);
349  len =
350  opus_encode_float (enc, pcm_buffer, FRAME_SIZE, opus_data,
352 
353  if (len < 0)
354  {
356  _ ("opus_encode_float() failed: %s. Aborting\n"),
357  opus_strerror (len));
358  quit (5);
359  }
360  if (((uint32_t) len) > UINT16_MAX - sizeof(struct AudioMessage))
361  {
362  GNUNET_break (0);
363  continue;
364  }
365 
366  /* As per OggOpus spec, granule is calculated as if the audio
367  had 48kHz sampling rate. */
369 
370  op.packet = (unsigned char *) opus_data;
371  op.bytes = len;
372  op.b_o_s = 0;
373  op.e_o_s = 0;
374  op.granulepos = enc_granulepos;
375  op.packetno = packet_id++;
376  ogg_stream_packetin (&os, &op);
377 
378  while (ogg_stream_flush_fill (&os, &og, PAGE_WATERLINE))
379  {
380  if (((unsigned long long) og.header_len)
381  + ((unsigned long long) og.body_len) >
382  UINT16_MAX - sizeof(struct AudioMessage))
383  {
384  GNUNET_assert (0);
385  continue;
386  }
387  write_page (&og);
388  }
389  }
390 
392  if (0 != new_size)
393  {
394  nbuf = pa_xmalloc (new_size);
395  memmove (nbuf,
397  new_size);
398  pa_xfree (transmit_buffer);
399  transmit_buffer = nbuf;
400  }
401  else
402  {
403  pa_xfree (transmit_buffer);
404  transmit_buffer = NULL;
405  }
407  transmit_buffer_length = new_size;
408 }
409 
410 
414 static void
415 stream_read_callback (pa_stream *s,
416  size_t length,
417  void *userdata)
418 {
419  const void *data;
420 
421  (void) userdata;
423  "Got %u/%d bytes of PCM data\n",
424  (unsigned int) length,
425  pcm_length);
426 
427  GNUNET_assert (NULL != s);
428  GNUNET_assert (length > 0);
429  if (stdio_event)
430  mainloop_api->io_enable (stdio_event, PA_IO_EVENT_OUTPUT);
431 
432  if (pa_stream_peek (s, (const void **) &data, &length) < 0)
433  {
435  _ ("pa_stream_peek() failed: %s\n"),
436  pa_strerror (pa_context_errno (context)));
437  quit (1);
438  return;
439  }
440  GNUNET_assert (NULL != data);
441  GNUNET_assert (length > 0);
442  if (NULL != transmit_buffer)
443  {
444  transmit_buffer = pa_xrealloc (transmit_buffer,
445  transmit_buffer_length + length);
447  data,
448  length);
449  transmit_buffer_length += length;
450  }
451  else
452  {
453  transmit_buffer = pa_xmalloc (length);
455  transmit_buffer_length = length;
457  }
458  pa_stream_drop (s);
459  packetizer ();
460 }
461 
462 
466 static void
467 exit_signal_callback (pa_mainloop_api *m,
468  pa_signal_event *e,
469  int sig,
470  void *userdata)
471 {
472  (void) m;
473  (void) e;
474  (void) sig;
475  (void) userdata;
477  _ ("Got signal, exiting.\n"));
478  quit (1);
479 }
480 
481 
485 static void
486 stream_state_callback (pa_stream *s,
487  void *userdata)
488 {
489  (void) userdata;
490  GNUNET_assert (NULL != s);
491  switch (pa_stream_get_state (s))
492  {
493  case PA_STREAM_CREATING:
494  case PA_STREAM_TERMINATED:
495  break;
496 
497  case PA_STREAM_READY:
498  {
499  const pa_buffer_attr *a;
500  char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
501  char sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
502 
504  _ ("Stream successfully created.\n"));
505 
506  if (! (a = pa_stream_get_buffer_attr (s)))
507  {
509  _ ("pa_stream_get_buffer_attr() failed: %s\n"),
510  pa_strerror (pa_context_errno
511  (pa_stream_get_context (s))));
512  }
513  else
514  {
516  _ ("Buffer metrics: maxlength=%u, fragsize=%u\n"),
517  a->maxlength, a->fragsize);
518  }
520  _ ("Using sample spec '%s', channel map '%s'.\n"),
521  pa_sample_spec_snprint (sst, sizeof(sst),
522  pa_stream_get_sample_spec (s)),
523  pa_channel_map_snprint (cmt, sizeof(cmt),
524  pa_stream_get_channel_map (s)));
525 
527  _ ("Connected to device %s (%u, %ssuspended).\n"),
528  pa_stream_get_device_name (s),
529  pa_stream_get_device_index (s),
530  pa_stream_is_suspended (s) ? "" : "not ");
531  }
532  break;
533 
534  case PA_STREAM_FAILED:
535  default:
537  _ ("Stream error: %s\n"),
538  pa_strerror (pa_context_errno (pa_stream_get_context (s))));
539  quit (1);
540  }
541 }
542 
543 
547 static void
548 context_state_callback (pa_context *c,
549  void *userdata)
550 {
551  (void) userdata;
552  GNUNET_assert (c);
553 
554  switch (pa_context_get_state (c))
555  {
556  case PA_CONTEXT_CONNECTING:
557  case PA_CONTEXT_AUTHORIZING:
558  case PA_CONTEXT_SETTING_NAME:
559  break;
560 
561  case PA_CONTEXT_READY:
562  {
563  int r;
564  pa_buffer_attr na;
565 
568  _ ("Connection established.\n"));
569  if (! (stream_in =
570  pa_stream_new (c, "GNUNET_VoIP recorder", &sample_spec, NULL)))
571  {
573  _ ("pa_stream_new() failed: %s\n"),
574  pa_strerror (pa_context_errno (c)));
575  goto fail;
576  }
577  pa_stream_set_state_callback (stream_in, &stream_state_callback, NULL);
578  pa_stream_set_read_callback (stream_in, &stream_read_callback, NULL);
579  memset (&na, 0, sizeof(na));
580  na.maxlength = UINT32_MAX;
581  na.fragsize = pcm_length;
582  if ((r = pa_stream_connect_record (stream_in, NULL, &na,
583  PA_STREAM_ADJUST_LATENCY)) < 0)
584  {
586  _ ("pa_stream_connect_record() failed: %s\n"),
587  pa_strerror (pa_context_errno (c)));
588  goto fail;
589  }
590 
591  break;
592  }
593 
594  case PA_CONTEXT_TERMINATED:
595  quit (0);
596  break;
597 
598  case PA_CONTEXT_FAILED:
599  default:
601  _ ("Connection failure: %s\n"),
602  pa_strerror (pa_context_errno (c)));
603  goto fail;
604  }
605  return;
606 
607 fail:
608  quit (1);
609 }
610 
611 
615 static void
617 {
618  int r;
619  int i;
620 
621  if (! pa_sample_spec_valid (&sample_spec))
622  {
624  _ ("Wrong Spec\n"));
625  }
626  /* set up main record loop */
627  if (! (m = pa_mainloop_new ()))
628  {
630  _ ("pa_mainloop_new() failed.\n"));
631  }
632  mainloop_api = pa_mainloop_get_api (m);
633 
634  /* listen to signals */
635  r = pa_signal_init (mainloop_api);
636  GNUNET_assert (r == 0);
637  pa_signal_new (SIGINT, &exit_signal_callback, NULL);
638  pa_signal_new (SIGTERM, &exit_signal_callback, NULL);
639 
640  /* connect to the main pulseaudio context */
641 
642  if (! (context = pa_context_new (mainloop_api, "GNUNET VoIP")))
643  {
645  _ ("pa_context_new() failed.\n"));
646  }
647  pa_context_set_state_callback (context, &context_state_callback, NULL);
648  if (pa_context_connect (context, NULL, 0, NULL) < 0)
649  {
651  _ ("pa_context_connect() failed: %s\n"),
652  pa_strerror (pa_context_errno (context)));
653  }
654  if (pa_mainloop_run (m, &i) < 0)
655  {
657  _ ("pa_mainloop_run() failed.\n"));
658  }
659 }
660 
661 
665 static void
667 {
668  int err;
669 
670  pcm_length = FRAME_SIZE * CHANNELS * sizeof(float);
671  pcm_buffer = pa_xmalloc (pcm_length);
673  enc = opus_encoder_create (SAMPLING_RATE,
674  CHANNELS,
676  &err);
677  opus_encoder_ctl (enc,
678  OPUS_SET_PACKET_LOSS_PERC (
680  opus_encoder_ctl (enc,
681  OPUS_SET_COMPLEXITY (CONV_OPUS_ENCODING_COMPLEXITY));
682  opus_encoder_ctl (enc,
683  OPUS_SET_INBAND_FEC (CONV_OPUS_INBAND_FEC));
684  opus_encoder_ctl (enc,
685  OPUS_SET_SIGNAL (CONV_OPUS_SIGNAL));
686 }
687 
688 
689 static void
691 {
692  int serialno;
693  struct OpusHeadPacket headpacket;
694  struct OpusCommentsPacket *commentspacket;
695  size_t commentspacket_len;
696 
698  0x7FFFFFFF);
699  /*Initialize Ogg stream struct*/
700  if (-1 == ogg_stream_init (&os, serialno))
701  {
703  _ ("ogg_stream_init() failed.\n"));
704  exit (3);
705  }
706 
707  packet_id = 0;
708 
709  /*Write header*/
710  {
711  ogg_packet op;
712  ogg_page og;
713  const char *opusver;
714  int vendor_length;
715 
716  GNUNET_memcpy (headpacket.magic, "OpusHead", 8);
717  headpacket.version = 1;
718  headpacket.channels = CHANNELS;
719  headpacket.preskip = GNUNET_htole16 (0);
721  headpacket.gain = GNUNET_htole16 (0);
722  headpacket.channel_mapping = 0; /* Mono or stereo */
723 
724  op.packet = (unsigned char *) &headpacket;
725  op.bytes = sizeof(headpacket);
726  op.b_o_s = 1;
727  op.e_o_s = 0;
728  op.granulepos = 0;
729  op.packetno = packet_id++;
730  ogg_stream_packetin (&os, &op);
731 
732  /* Head packet must be alone on its page */
733  while (ogg_stream_flush (&os, &og))
734  {
735  write_page (&og);
736  }
737 
738  commentspacket_len = sizeof(*commentspacket);
739  opusver = opus_get_version_string ();
740  vendor_length = strlen (opusver);
741  commentspacket_len += vendor_length;
742  commentspacket_len += sizeof(uint32_t);
743 
744  commentspacket = (struct OpusCommentsPacket *) malloc (commentspacket_len);
745  if (NULL == commentspacket)
746  {
748  _ ("Failed to allocate %u bytes for second packet\n"),
749  (unsigned int) commentspacket_len);
750  exit (5);
751  }
752 
753  GNUNET_memcpy (commentspacket->magic, "OpusTags", 8);
754  commentspacket->vendor_length = GNUNET_htole32 (vendor_length);
755  GNUNET_memcpy (&commentspacket[1], opusver, vendor_length);
756  *(uint32_t *) &((char *) &commentspacket[1])[vendor_length] = \
757  GNUNET_htole32 (0); /* no tags */
758 
759  op.packet = (unsigned char *) commentspacket;
760  op.bytes = commentspacket_len;
761  op.b_o_s = 0;
762  op.e_o_s = 0;
763  op.granulepos = 0;
764  op.packetno = packet_id++;
765  ogg_stream_packetin (&os, &op);
766 
767  /* Comment packets must not be mixed with audio packets on their pages */
768  while (ogg_stream_flush (&os, &og))
769  {
770  write_page (&og);
771  }
772 
773  free (commentspacket);
774  }
775 }
776 
777 
785 int
786 main (int argc,
787  char *argv[])
788 {
789  (void) argc;
790  (void) argv;
792  GNUNET_log_setup ("gnunet-helper-audio-record",
793  "WARNING",
794  NULL));
796  "Audio source starts\n");
797  audio_message = GNUNET_malloc (UINT16_MAX);
799 
800 #ifdef DEBUG_RECORD_PURE_OGG
801  dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0;
802 #endif
803  ogg_init ();
804  opus_init ();
805  pa_init ();
806  return 0;
807 }
constants for network protocols
char * getenv()
static int ret
Return value of the commandline.
Definition: gnunet-abd.c:81
static struct GNUNET_ARM_Operation * op
Current operation.
Definition: gnunet-arm.c:144
static struct Experiment * e
#define CONV_OPUS_INBAND_FEC
Configures the encoder's use of inband forward error correction (FEC).
static pa_stream * stream_in
Pulseaudio recording stream.
#define CONV_OPUS_SIGNAL
Configures the type of signal being encoded.
static int dump_pure_ogg
1 to not to write GNUnet message headers, producing pure playable ogg output
int main(int argc, char *argv[])
The main function for the record helper.
static struct AudioMessage * audio_message
Audio message skeleton.
#define SAMPLING_RATE
Sampling rate.
static void stream_read_callback(pa_stream *s, size_t length, void *userdata)
Pulseaudio callback when new data is available.
#define CHANNELS
Number of channels.
static void quit(int ret)
Pulseaudio shutdown task.
static pa_context * context
Pulseaudio context.
#define MAX_PAYLOAD_BYTES
Maximum length of opus payload.
static size_t transmit_buffer_index
Read index for transmit buffer.
#define PAGE_WATERLINE
Pages are committed when their size goes over this value.
#define CONV_OPUS_APP_TYPE
Coding mode.
static void pa_init()
Pulsaudio init.
static float * pcm_buffer
PCM data buffer for one OPUS frame.
static GNUNET_NETWORK_STRUCT_END pa_mainloop_api * mainloop_api
Pulseaudio mainloop api.
static void ogg_init()
static void stream_state_callback(pa_stream *s, void *userdata)
Pulseaudio stream state callback.
#define CONV_OPUS_ENCODING_COMPLEXITY
Configures the encoder's computational complexity.
static void opus_init()
OPUS init.
static size_t transmit_buffer_length
Length of audio buffer.
static char * transmit_buffer
Audio buffer.
static pa_mainloop * m
Pulseaudio mainloop.
#define CONV_OPUS_PACKET_LOSS_PERCENTAGE
Configures the encoder's expected packet loss percentage.
static void write_page(ogg_page *og)
static void context_state_callback(pa_context *c, void *userdata)
Pulseaudio context state callback.
static void packetizer()
Creates OPUS packets from PCM data.
static unsigned char * opus_data
Buffer for encoded data.
static OpusEncoder * enc
OPUS encoder.
static pa_io_event * stdio_event
Pulseaudio io events.
static pa_sample_spec sample_spec
Specification for recording.
static int32_t packet_id
Ogg packet id.
static ogg_stream_state os
Ogg muxer state.
static void write_data(const char *ptr, size_t msg_size)
static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata)
Exit callback for SIGTERM and SIGINT.
#define FRAME_SIZE
How many samples to buffer before encoding them.
static int64_t enc_granulepos
Ogg granule for current packet.
static int pcm_length
Length of the pcm data needed for one OPUS frame.
uint32_t data
The data value.
uint16_t len
length of data (which is always a uint32_t, but presumably this can be used to specify that fewer byt...
Core service; the main API for encrypted P2P communications.
Constants for network protocols.
uint32_t GNUNET_CRYPTO_random_u32(enum GNUNET_CRYPTO_Quality mode, uint32_t i)
Produce a random value.
@ GNUNET_CRYPTO_QUALITY_STRONG
High-quality operations are desired.
#define GNUNET_NETWORK_STRUCT_BEGIN
Define as empty, GNUNET_PACKED should suffice, but this won't work on W32.
#define GNUNET_htole32(x)
#define GNUNET_log(kind,...)
#define GNUNET_NETWORK_STRUCT_END
Define as empty, GNUNET_PACKED should suffice, but this won't work on W32;.
#define GNUNET_memcpy(dst, src, n)
Call memcpy() but check for n being 0 first.
#define GNUNET_htole16(x)
#define GNUNET_PACKED
gcc-ism to get packed structs.
@ GNUNET_OK
int GNUNET_log_setup(const char *comp, const char *loglevel, const char *logfile)
Setup logging.
#define GNUNET_assert(cond)
Use this for fatal errors that cannot be handled.
#define GNUNET_break(cond)
Use this for internal assertion violations that are not fatal (can be handled) but should not occur.
#define GNUNET_log_strerror(level, cmd)
Log an error message at log-level 'level' that indicates a failure of the command 'cmd' with the mess...
@ GNUNET_ERROR_TYPE_ERROR
@ GNUNET_ERROR_TYPE_DEBUG
@ GNUNET_ERROR_TYPE_INFO
#define GNUNET_malloc(size)
Wrapper around malloc.
#define GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO
Message to transmit the audio between helper and speaker/microphone library.
#define _(String)
GNU gettext support macro.
Definition: platform.h:177
Message to transmit the audio (between client and helpers).
Definition: conversation.h:59
struct GNUNET_MessageHeader header
Type is GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO.
Definition: conversation.h:63
uint16_t type
The type of the message (GNUNET_MESSAGE_TYPE_XXXX), in big-endian format.
uint16_t size
The length of the struct (in bytes, including the length field itself), in big-endian format.