GNUnet 0.21.0
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
143static 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 */
152struct OpusHeadPacket
153{
154 uint8_t magic[8];
155 uint8_t version;
156 uint8_t channels;
157 uint16_t preskip GNUNET_PACKED;
159 uint16_t gain GNUNET_PACKED;
160 uint8_t channel_mapping;
161};
162
164{
165 uint8_t magic[8];
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
181static pa_mainloop_api *mainloop_api;
182
186static pa_mainloop *m;
187
191static pa_context *context;
192
196static pa_stream *stream_in;
197
201static pa_io_event *stdio_event;
202
206static OpusEncoder *enc;
207
211static unsigned char *opus_data;
212
216static float *pcm_buffer;
217
221static int pcm_length;
222
226static char *transmit_buffer;
227
232
237
242
246static ogg_stream_state os;
247
251static int32_t packet_id;
252
256static int64_t enc_granulepos;
257
258#ifdef DEBUG_RECORD_PURE_OGG
263static int dump_pure_ogg;
264#endif
265
269static void
271{
273 ret);
274 exit (ret);
275}
276
277
278static void
279write_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
303static void
304write_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
334static 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
414static void
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
466static void
467exit_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
485static void
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
547static void
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
607fail:
608 quit (1);
609}
610
611
615static 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
665static 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
689static 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
785int
786main (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 struct GNUNET_ARM_Operation * op
Current operation.
Definition: gnunet-arm.c:144
static int ret
Final status code.
Definition: gnunet-arm.c:94
static char * data
The data to insert into the dht.
#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.
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
#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.
enum GNUNET_GenericReturnValue GNUNET_log_setup(const char *comp, const char *loglevel, const char *logfile)
Setup logging.
#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:178
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.