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  uint8_t magic[8];
154  uint8_t version;
155  uint8_t channels;
156  uint16_t preskip GNUNET_PACKED;
157  uint32_t sampling_rate GNUNET_PACKED;
158  uint16_t gain GNUNET_PACKED;
159  uint8_t channel_mapping;
160 };
161 
163  uint8_t magic[8];
164  uint32_t vendor_length;
165  /* followed by:
166  char vendor[vendor_length];
167  uint32_t string_count;
168  followed by @a string_count pairs of:
169  uint32_t string_length;
170  char string[string_length];
171  */
172 };
173 
175 
179 static pa_mainloop_api *mainloop_api;
180 
184 static pa_mainloop *m;
185 
189 static pa_context *context;
190 
194 static pa_stream *stream_in;
195 
199 static pa_io_event *stdio_event;
200 
204 static OpusEncoder *enc;
205 
209 static unsigned char *opus_data;
210 
214 static float *pcm_buffer;
215 
219 static int pcm_length;
220 
224 static char *transmit_buffer;
225 
230 
234 static size_t transmit_buffer_index;
235 
240 
244 static ogg_stream_state os;
245 
249 static int32_t packet_id;
250 
254 static int64_t enc_granulepos;
255 
256 #ifdef DEBUG_RECORD_PURE_OGG
257 
261 static int dump_pure_ogg;
262 #endif
263 
267 static void
268 quit(int ret)
269 {
271  ret);
272  exit(ret);
273 }
274 
275 
276 static void
277 write_data(const char *ptr,
278  size_t msg_size)
279 {
280  ssize_t ret;
281  size_t off;
282 
283  off = 0;
284  while (off < msg_size)
285  {
286  ret = write(STDOUT_FILENO,
287  &ptr[off],
288  msg_size - off);
289  if (0 >= ret)
290  {
291  if (-1 == ret)
293  "write");
294  quit(2);
295  }
296  off += ret;
297  }
298 }
299 
300 
301 static void
302 write_page(ogg_page *og)
303 {
304  static unsigned long long toff;
305  size_t msg_size;
306 
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 
494  case PA_STREAM_READY:
495  {
496  const pa_buffer_attr *a;
497  char cmt[PA_CHANNEL_MAP_SNPRINT_MAX];
498  char sst[PA_SAMPLE_SPEC_SNPRINT_MAX];
499 
501  _("Stream successfully created.\n"));
502 
503  if (!(a = pa_stream_get_buffer_attr(s)))
504  {
506  _("pa_stream_get_buffer_attr() failed: %s\n"),
507  pa_strerror(pa_context_errno
508  (pa_stream_get_context(s))));
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 
531  case PA_STREAM_FAILED:
532  default:
534  _("Stream error: %s\n"),
535  pa_strerror(pa_context_errno(pa_stream_get_context(s))));
536  quit(1);
537  }
538 }
539 
540 
544 static void
545 context_state_callback(pa_context * c,
546  void *userdata)
547 {
548  (void)userdata;
549  GNUNET_assert(c);
550 
551  switch (pa_context_get_state(c))
552  {
553  case PA_CONTEXT_CONNECTING:
554  case PA_CONTEXT_AUTHORIZING:
555  case PA_CONTEXT_SETTING_NAME:
556  break;
557 
558  case PA_CONTEXT_READY:
559  {
560  int r;
561  pa_buffer_attr na;
562 
565  _("Connection established.\n"));
566  if (!(stream_in =
567  pa_stream_new(c, "GNUNET_VoIP recorder", &sample_spec, NULL)))
568  {
570  _("pa_stream_new() failed: %s\n"),
571  pa_strerror(pa_context_errno(c)));
572  goto fail;
573  }
574  pa_stream_set_state_callback(stream_in, &stream_state_callback, NULL);
575  pa_stream_set_read_callback(stream_in, &stream_read_callback, NULL);
576  memset(&na, 0, sizeof(na));
577  na.maxlength = UINT32_MAX;
578  na.fragsize = pcm_length;
579  if ((r = pa_stream_connect_record(stream_in, NULL, &na,
580  PA_STREAM_ADJUST_LATENCY)) < 0)
581  {
583  _("pa_stream_connect_record() failed: %s\n"),
584  pa_strerror(pa_context_errno(c)));
585  goto fail;
586  }
587 
588  break;
589  }
590 
591  case PA_CONTEXT_TERMINATED:
592  quit(0);
593  break;
594 
595  case PA_CONTEXT_FAILED:
596  default:
598  _("Connection failure: %s\n"),
599  pa_strerror(pa_context_errno(c)));
600  goto fail;
601  }
602  return;
603 
604 fail:
605  quit(1);
606 }
607 
608 
612 static void
614 {
615  int r;
616  int i;
617 
618  if (!pa_sample_spec_valid(&sample_spec))
619  {
621  _("Wrong Spec\n"));
622  }
623  /* set up main record loop */
624  if (!(m = pa_mainloop_new()))
625  {
627  _("pa_mainloop_new() failed.\n"));
628  }
629  mainloop_api = pa_mainloop_get_api(m);
630 
631  /* listen to signals */
632  r = pa_signal_init(mainloop_api);
633  GNUNET_assert(r == 0);
634  pa_signal_new(SIGINT, &exit_signal_callback, NULL);
635  pa_signal_new(SIGTERM, &exit_signal_callback, NULL);
636 
637  /* connect to the main pulseaudio context */
638 
639  if (!(context = pa_context_new(mainloop_api, "GNUNET VoIP")))
640  {
642  _("pa_context_new() failed.\n"));
643  }
644  pa_context_set_state_callback(context, &context_state_callback, NULL);
645  if (pa_context_connect(context, NULL, 0, NULL) < 0)
646  {
648  _("pa_context_connect() failed: %s\n"),
649  pa_strerror(pa_context_errno(context)));
650  }
651  if (pa_mainloop_run(m, &i) < 0)
652  {
654  _("pa_mainloop_run() failed.\n"));
655  }
656 }
657 
658 
662 static void
664 {
665  int err;
666 
667  pcm_length = FRAME_SIZE * CHANNELS * sizeof(float);
668  pcm_buffer = pa_xmalloc(pcm_length);
670  enc = opus_encoder_create(SAMPLING_RATE,
671  CHANNELS,
673  &err);
674  opus_encoder_ctl(enc,
675  OPUS_SET_PACKET_LOSS_PERC(CONV_OPUS_PACKET_LOSS_PERCENTAGE));
676  opus_encoder_ctl(enc,
677  OPUS_SET_COMPLEXITY(CONV_OPUS_ENCODING_COMPLEXITY));
678  opus_encoder_ctl(enc,
679  OPUS_SET_INBAND_FEC(CONV_OPUS_INBAND_FEC));
680  opus_encoder_ctl(enc,
681  OPUS_SET_SIGNAL(CONV_OPUS_SIGNAL));
682 }
683 
684 
685 static void
687 {
688  int serialno;
689  struct OpusHeadPacket headpacket;
690  struct OpusCommentsPacket *commentspacket;
691  size_t commentspacket_len;
692 
694  0x7FFFFFFF);
695  /*Initialize Ogg stream struct*/
696  if (-1 == ogg_stream_init(&os, serialno))
697  {
699  _("ogg_stream_init() failed.\n"));
700  exit(3);
701  }
702 
703  packet_id = 0;
704 
705  /*Write header*/
706  {
707  ogg_packet op;
708  ogg_page og;
709  const char *opusver;
710  int vendor_length;
711 
712  GNUNET_memcpy(headpacket.magic, "OpusHead", 8);
713  headpacket.version = 1;
714  headpacket.channels = CHANNELS;
715  headpacket.preskip = GNUNET_htole16(0);
717  headpacket.gain = GNUNET_htole16(0);
718  headpacket.channel_mapping = 0; /* Mono or stereo */
719 
720  op.packet = (unsigned char *)&headpacket;
721  op.bytes = sizeof(headpacket);
722  op.b_o_s = 1;
723  op.e_o_s = 0;
724  op.granulepos = 0;
725  op.packetno = packet_id++;
726  ogg_stream_packetin(&os, &op);
727 
728  /* Head packet must be alone on its page */
729  while (ogg_stream_flush(&os, &og))
730  {
731  write_page(&og);
732  }
733 
734  commentspacket_len = sizeof(*commentspacket);
735  opusver = opus_get_version_string();
736  vendor_length = strlen(opusver);
737  commentspacket_len += vendor_length;
738  commentspacket_len += sizeof(uint32_t);
739 
740  commentspacket = (struct OpusCommentsPacket *)malloc(commentspacket_len);
741  if (NULL == commentspacket)
742  {
744  _("Failed to allocate %u bytes for second packet\n"),
745  (unsigned int)commentspacket_len);
746  exit(5);
747  }
748 
749  GNUNET_memcpy(commentspacket->magic, "OpusTags", 8);
750  commentspacket->vendor_length = GNUNET_htole32(vendor_length);
751  GNUNET_memcpy(&commentspacket[1], opusver, vendor_length);
752  *(uint32_t *)&((char *)&commentspacket[1])[vendor_length] = \
753  GNUNET_htole32(0); /* no tags */
754 
755  op.packet = (unsigned char *)commentspacket;
756  op.bytes = commentspacket_len;
757  op.b_o_s = 0;
758  op.e_o_s = 0;
759  op.granulepos = 0;
760  op.packetno = packet_id++;
761  ogg_stream_packetin(&os, &op);
762 
763  /* Comment packets must not be mixed with audio packets on their pages */
764  while (ogg_stream_flush(&os, &og))
765  {
766  write_page(&og);
767  }
768 
769  free(commentspacket);
770  }
771 }
772 
780 int
781 main(int argc,
782  char *argv[])
783 {
784  (void)argc;
785  (void)argv;
787  GNUNET_log_setup("gnunet-helper-audio-record",
788  "WARNING",
789  NULL));
791  "Audio source starts\n");
792  audio_message = GNUNET_malloc(UINT16_MAX);
793  audio_message->header.type = htons(GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO);
794 
795 #ifdef DEBUG_RECORD_PURE_OGG
796  dump_pure_ogg = getenv("GNUNET_RECORD_PURE_OGG") ? 1 : 0;
797 #endif
798  ogg_init();
799  opus_init();
800  pa_init();
801  return 0;
802 }
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:59
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 GNUNET_memcpy(dst, src, n)
Call memcpy() but check for n being 0 first.
#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:75
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:181
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.
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.