X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fconversation%2Fgnunet-helper-audio-record.c;h=9caad611ba60ccf7379bc6ac9e630919cac8b49f;hb=db9c68c76d7a9eb3f200df9d0fcaf971387cb061;hp=a3e760794f122ba93b87e0e589ecb84643e903e8;hpb=875342e4d0114e626dcf502362787d048d5e12fc;p=oweals%2Fgnunet.git diff --git a/src/conversation/gnunet-helper-audio-record.c b/src/conversation/gnunet-helper-audio-record.c index a3e760794..9caad611b 100644 --- a/src/conversation/gnunet-helper-audio-record.c +++ b/src/conversation/gnunet-helper-audio-record.c @@ -18,10 +18,11 @@ Boston, MA 02111-1307, USA. */ /** - * @file conversation/gnunet-helper-audio-playback.c - * @brief constants for network protocols + * @file conversation/gnunet-helper-audio-record.c + * @brief program to record audio data from the microphone * @author Siomon Dieterle * @author Andreas Fuchs + * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" @@ -37,17 +38,143 @@ #include #include #include +#include +#define DEBUG_RECORD_PURE_OGG 1 + +/** + * Sampling rate + */ +#define SAMPLING_RATE 48000 + +/** + * How many ms of audio to buffer before encoding them. + * Possible values: + * 60, 40, 20, 10, 5, 2.5 + */ +#define FRAME_SIZE_MS 40 + +/** + * How many samples to buffer before encoding them. + */ +#define FRAME_SIZE (SAMPLING_RATE / 1000 * FRAME_SIZE_MS) + +/** + * Pages are commited when their size goes over this value. + * Note that in practice we flush pages VERY often (every frame), + * which means that pages NEVER really get to be this big. + * With one-packet-per-page, pages are roughly 100-300 bytes each. + * + * This value is chosen to make MAX_PAYLOAD_BYTES=1024 fit + * into a single page. + */ +#define PAGE_WATERLINE 800 + +/** + * Maximum length of opus payload + */ +#define MAX_PAYLOAD_BYTES 1024 + +/** + * Number of channels + */ +#define CHANNELS 1 + +/** + * Configures the encoder's expected packet loss percentage. + * + * Higher values will trigger progressively more loss resistant behavior + * in the encoder at the expense of quality at a given bitrate + * in the lossless case, but greater quality under loss. + */ +#define CONV_OPUS_PACKET_LOSS_PERCENTAGE 1 + +/** + * Configures the encoder's computational complexity. + * + * The supported range is 0-10 inclusive with 10 representing + * the highest complexity. + */ +#define CONV_OPUS_ENCODING_COMPLEXITY 10 + +/** + * Configures the encoder's use of inband forward error correction (FEC). + * + * Note: This is only applicable to the LPC layer. + */ +#define CONV_OPUS_INBAND_FEC 1 + +/** + * Configures the type of signal being encoded. + * + * This is a hint which helps the encoder's mode selection. + * + * Possible values: + * OPUS_AUTO - (default) Encoder detects the type automatically. + * OPUS_SIGNAL_VOICE - Bias thresholds towards choosing LPC or Hybrid modes. + * OPUS_SIGNAL_MUSIC - Bias thresholds towards choosing MDCT modes. + */ +#define CONV_OPUS_SIGNAL OPUS_AUTO + +/** + * Coding mode. + * + * Possible values: + * OPUS_APPLICATION_VOIP - gives best quality at a given bitrate for voice + * signals. It enhances the input signal by high-pass filtering and + * emphasizing formants and harmonics. Optionally it includes in-band forward + * error correction to protect against packet loss. Use this mode for typical + * VoIP applications. Because of the enhancement, even at high bitrates + * the output may sound different from the input. + * OPUS_APPLICATION_AUDIO - gives best quality at a given bitrate for most + * non-voice signals like music. Use this mode for music and mixed + * (music/voice) content, broadcast, and applications requiring less than + * 15 ms of coding delay. + * OPUS_APPLICATION_RESTRICTED_LOWDELAY - configures low-delay mode that + * disables the speech-optimized mode in exchange for slightly reduced delay. + * This mode can only be set on an newly initialized or freshly reset encoder + * because it changes the codec delay. + */ +#define CONV_OPUS_APP_TYPE OPUS_APPLICATION_VOIP /** * Specification for recording. May change in the future to spec negotiation. */ static pa_sample_spec sample_spec = { .format = PA_SAMPLE_FLOAT32LE, - .rate = 48000, - .channels = 1 + .rate = SAMPLING_RATE, + .channels = CHANNELS +}; + +GNUNET_NETWORK_STRUCT_BEGIN + +/* OggOpus spec says the numbers must be in little-endian order */ +struct OpusHeadPacket +{ + uint8_t magic[8]; + uint8_t version; + uint8_t channels; + uint16_t preskip GNUNET_PACKED; + uint32_t sampling_rate GNUNET_PACKED; + uint16_t gain GNUNET_PACKED; + uint8_t channel_mapping; +}; + +struct OpusCommentsPacket +{ + uint8_t magic[8]; + uint32_t vendor_length; + /* followed by: + char vendor[vendor_length]; + uint32_t string_count; + followed by @a string_count pairs of: + uint32_t string_length; + char string[string_length]; + */ }; +GNUNET_NETWORK_STRUCT_END + /** * Pulseaudio mainloop api */ @@ -79,7 +206,7 @@ static pa_io_event *stdio_event; static OpusEncoder *enc; /** - * + * Buffer for encoded data */ static unsigned char *opus_data; @@ -89,24 +216,14 @@ static unsigned char *opus_data; static float *pcm_buffer; /** - * Length of the pcm data needed for one OPUS frame + * Length of the pcm data needed for one OPUS frame */ static int pcm_length; -/** - * Number of samples for one frame - */ -static int frame_size; - -/** -* Maximum length of opus payload -*/ -static int max_payload_bytes = 1500; - /** * Audio buffer */ -static void *transmit_buffer; +static char *transmit_buffer; /** * Length of audio buffer @@ -123,6 +240,28 @@ static size_t transmit_buffer_index; */ static struct AudioMessage *audio_message; +/** + * Ogg muxer state + */ +static ogg_stream_state os; + +/** + * Ogg packet id + */ +static int32_t packet_id; + +/** + * Ogg granule for current packet + */ +static int64_t enc_granulepos; + +#ifdef DEBUG_RECORD_PURE_OGG +/** + * 1 to not to write GNUnet message headers, + * producing pure playable ogg output + */ +static int dump_pure_ogg; +#endif /** * Pulseaudio shutdown task @@ -135,52 +274,123 @@ quit (int ret) } +static void +write_data (const char *ptr, size_t msg_size) +{ + ssize_t ret; + size_t off; + off = 0; + while (off < msg_size) + { + ret = write (1, &ptr[off], msg_size - off); + if (0 >= ret) + { + if (-1 == ret) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "write"); + quit (2); + } + off += ret; + } +} + +static void +write_page (ogg_page *og) +{ + static unsigned long long toff; + size_t msg_size; + msg_size = sizeof (struct AudioMessage) + og->header_len + og->body_len; + audio_message->header.size = htons ((uint16_t) msg_size); + memcpy (&audio_message[1], og->header, og->header_len); + memcpy (((char *) &audio_message[1]) + og->header_len, og->body, og->body_len); + + toff += msg_size; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending %u bytes of audio data (total: %llu)\n", + (unsigned int) msg_size, + toff); +#ifdef DEBUG_RECORD_PURE_OGG + if (dump_pure_ogg) + write_data ((const char *) &audio_message[1], og->header_len + og->body_len); + else +#endif + write_data ((const char *) audio_message, msg_size); +} + /** * Creates OPUS packets from PCM data */ static void packetizer () { + char *nbuf; + size_t new_size; + int32_t len; + ogg_packet op; + ogg_page og; + while (transmit_buffer_length >= transmit_buffer_index + pcm_length) { - ssize_t ret; - int len; // FIXME: int? - size_t msg_size; - memcpy (pcm_buffer, - (float *) transmit_buffer + - (transmit_buffer_index / sizeof (float)), pcm_length); + &transmit_buffer[transmit_buffer_index], + pcm_length); + transmit_buffer_index += pcm_length; len = - opus_encode_float (enc, pcm_buffer, frame_size, opus_data, - max_payload_bytes); + opus_encode_float (enc, pcm_buffer, FRAME_SIZE, opus_data, + MAX_PAYLOAD_BYTES); + + if (len < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("opus_encode_float() failed: %s. Aborting\n"), + opus_strerror (len)); + quit (5); + } if (len > UINT16_MAX - sizeof (struct AudioMessage)) { GNUNET_break (0); - len = UINT16_MAX - sizeof (struct AudioMessage); + continue; } - msg_size = sizeof (struct AudioMessage) + len; - audio_message->header.size = htons ((uint16_t) msg_size); - memcpy (&audio_message[1], opus_data, len); - // FIXME: handle partial writes better... - if ((ret = write (1, audio_message, msg_size)) != msg_size) + /* As per OggOpus spec, granule is calculated as if the audio + had 48kHz sampling rate. */ + enc_granulepos += FRAME_SIZE * 48000 / SAMPLING_RATE; + + op.packet = (unsigned char *) opus_data; + op.bytes = len; + op.b_o_s = 0; + op.e_o_s = 0; + op.granulepos = enc_granulepos; + op.packetno = packet_id++; + ogg_stream_packetin (&os, &op); + + while (ogg_stream_flush_fill (&os, &og, PAGE_WATERLINE)) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("write")); - return; - } - transmit_buffer_index += pcm_length; + if (og.header_len + og.body_len > UINT16_MAX - sizeof (struct AudioMessage)) + { + GNUNET_assert (0); + continue; + } + write_page (&og); + } } - int new_size = transmit_buffer_length - transmit_buffer_index; + new_size = transmit_buffer_length - transmit_buffer_index; if (0 != new_size) { - transmit_buffer = pa_xrealloc (transmit_buffer, new_size); - memcpy (transmit_buffer, transmit_buffer + transmit_buffer_index, - new_size); - - transmit_buffer_index = 0; - transmit_buffer_length = new_size; + nbuf = pa_xmalloc (new_size); + memmove (nbuf, + &transmit_buffer[transmit_buffer_index], + new_size); + pa_xfree (transmit_buffer); + transmit_buffer = nbuf; + } + else + { + pa_xfree (transmit_buffer); + transmit_buffer = NULL; } + transmit_buffer_index = 0; + transmit_buffer_length = new_size; } @@ -188,31 +398,38 @@ packetizer () * Pulseaudio callback when new data is available. */ static void -stream_read_callback (pa_stream * s, size_t length, void *userdata) +stream_read_callback (pa_stream * s, + size_t length, + void *userdata) { const void *data; - GNUNET_assert (s); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got %u/%u bytes of PCM data\n", + length, + pcm_length); + + GNUNET_assert (NULL != s); GNUNET_assert (length > 0); if (stdio_event) mainloop_api->io_enable (stdio_event, PA_IO_EVENT_OUTPUT); if (pa_stream_peek (s, (const void **) &data, &length) < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_stream_peek() failed: %s\n"), - pa_strerror (pa_context_errno (context))); - quit (1); - return; - } - - GNUNET_assert (data); + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_stream_peek() failed: %s\n"), + pa_strerror (pa_context_errno (context))); + quit (1); + return; + } + GNUNET_assert (NULL != data); GNUNET_assert (length > 0); - - if (transmit_buffer) + if (NULL != transmit_buffer) { - transmit_buffer = - pa_xrealloc (transmit_buffer, transmit_buffer_length + length); - memcpy ((uint8_t *) transmit_buffer + transmit_buffer_length, data, + transmit_buffer = pa_xrealloc (transmit_buffer, + transmit_buffer_length + length); + memcpy (&transmit_buffer[transmit_buffer_length], + data, length); transmit_buffer_length += length; } @@ -232,12 +449,12 @@ stream_read_callback (pa_stream * s, size_t length, void *userdata) * Exit callback for SIGTERM and SIGINT */ static void -exit_signal_callback (pa_mainloop_api * m, - pa_signal_event * e, +exit_signal_callback (pa_mainloop_api * m, + pa_signal_event * e, int sig, void *userdata) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, + GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Got signal, exiting.\n")); quit (1); } @@ -249,61 +466,57 @@ exit_signal_callback (pa_mainloop_api * m, static void stream_state_callback (pa_stream * s, void *userdata) { - GNUNET_assert (s); + GNUNET_assert (NULL != s); switch (pa_stream_get_state (s)) + { + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + case PA_STREAM_READY: { - case PA_STREAM_CREATING: - case PA_STREAM_TERMINATED: - break; - - case PA_STREAM_READY: - if (1) - { - const pa_buffer_attr *a; - char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], - sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Stream successfully created.\n")); - - if (!(a = pa_stream_get_buffer_attr (s))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("pa_stream_get_buffer_attr() failed: %s\n"), - pa_strerror (pa_context_errno - (pa_stream_get_context (s)))); - - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Buffer metrics: maxlength=%u, fragsize=%u\n"), - a->maxlength, a->fragsize); - } - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Using sample spec '%s', channel map '%s'.\n"), - pa_sample_spec_snprint (sst, sizeof (sst), - pa_stream_get_sample_spec (s)), - pa_channel_map_snprint (cmt, sizeof (cmt), - pa_stream_get_channel_map (s))); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Connected to device %s (%u, %ssuspended).\n"), - pa_stream_get_device_name (s), - pa_stream_get_device_index (s), - pa_stream_is_suspended (s) ? "" : "not "); - } - - break; - - case PA_STREAM_FAILED: - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Stream error: %s\n"), - pa_strerror (pa_context_errno (pa_stream_get_context (s)))); - quit (1); + const pa_buffer_attr *a; + char cmt[PA_CHANNEL_MAP_SNPRINT_MAX]; + char sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Stream successfully created.\n")); + + if (!(a = pa_stream_get_buffer_attr (s))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_stream_get_buffer_attr() failed: %s\n"), + pa_strerror (pa_context_errno + (pa_stream_get_context (s)))); + + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Buffer metrics: maxlength=%u, fragsize=%u\n"), + a->maxlength, a->fragsize); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Using sample spec '%s', channel map '%s'.\n"), + pa_sample_spec_snprint (sst, sizeof (sst), + pa_stream_get_sample_spec (s)), + pa_channel_map_snprint (cmt, sizeof (cmt), + pa_stream_get_channel_map (s))); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Connected to device %s (%u, %ssuspended).\n"), + pa_stream_get_device_name (s), + pa_stream_get_device_index (s), + pa_stream_is_suspended (s) ? "" : "not "); } + break; + case PA_STREAM_FAILED: + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Stream error: %s\n"), + pa_strerror (pa_context_errno (pa_stream_get_context (s)))); + quit (1); + } } @@ -317,58 +530,53 @@ context_state_callback (pa_context * c, GNUNET_assert (c); switch (pa_context_get_state (c)) + { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + case PA_CONTEXT_READY: + { + int r; + pa_buffer_attr na; + + GNUNET_assert (!stream_in); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Connection established.\n")); + if (! (stream_in = + pa_stream_new (c, "GNUNET_VoIP recorder", &sample_spec, NULL))) { - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - - case PA_CONTEXT_READY: - { - int r; - - GNUNET_assert (c); - GNUNET_assert (!stream_in); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Connection established.\n")); - - if (! - (stream_in = - pa_stream_new (c, "GNUNET_VoIP recorder", &sample_spec, NULL))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("pa_stream_new() failed: %s\n"), - pa_strerror (pa_context_errno (c))); - goto fail; - } - - - pa_stream_set_state_callback (stream_in, stream_state_callback, NULL); - pa_stream_set_read_callback (stream_in, stream_read_callback, NULL); - - - if ((r = pa_stream_connect_record (stream_in, NULL, NULL, 0)) < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("pa_stream_connect_record() failed: %s\n"), - pa_strerror (pa_context_errno (c))); - goto fail; - } - - break; - } - - case PA_CONTEXT_TERMINATED: - quit (0); - break; - - case PA_CONTEXT_FAILED: - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Connection failure: %s\n"), + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_stream_new() failed: %s\n"), + pa_strerror (pa_context_errno (c))); + goto fail; + } + pa_stream_set_state_callback (stream_in, &stream_state_callback, NULL); + pa_stream_set_read_callback (stream_in, &stream_read_callback, NULL); + memset (&na, 0, sizeof (na)); + na.maxlength = UINT32_MAX; + na.fragsize = pcm_length; + if ((r = pa_stream_connect_record (stream_in, NULL, &na, + PA_STREAM_ADJUST_LATENCY)) < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_stream_connect_record() failed: %s\n"), pa_strerror (pa_context_errno (c))); goto fail; } + break; + } + case PA_CONTEXT_TERMINATED: + quit (0); + break; + case PA_CONTEXT_FAILED: + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Connection failure: %s\n"), + pa_strerror (pa_context_errno (c))); + goto fail; + } return; fail: @@ -386,46 +594,43 @@ pa_init () int i; if (!pa_sample_spec_valid (&sample_spec)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Wrong Spec\n")); - } - + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Wrong Spec\n")); + } /* set up main record loop */ - if (!(m = pa_mainloop_new ())) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_new() failed.\n")); - } - + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_mainloop_new() failed.\n")); + } mainloop_api = pa_mainloop_get_api (m); /* listen to signals */ - r = pa_signal_init (mainloop_api); GNUNET_assert (r == 0); - pa_signal_new (SIGINT, exit_signal_callback, NULL); - pa_signal_new (SIGTERM, exit_signal_callback, NULL); + pa_signal_new (SIGINT, &exit_signal_callback, NULL); + pa_signal_new (SIGTERM, &exit_signal_callback, NULL); /* connect to the main pulseaudio context */ if (!(context = pa_context_new (mainloop_api, "GNUNET VoIP"))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_context_new() failed.\n")); - } - - pa_context_set_state_callback (context, context_state_callback, NULL); - + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_context_new() failed.\n")); + } + pa_context_set_state_callback (context, &context_state_callback, NULL); if (pa_context_connect (context, NULL, 0, NULL) < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("pa_context_connect() failed: %s\n"), - pa_strerror (pa_context_errno (context))); - } - + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_context_connect() failed: %s\n"), + pa_strerror (pa_context_errno (context))); + } if (pa_mainloop_run (m, &i) < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_run() failed.\n")); - } + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("pa_mainloop_run() failed.\n")); + } } @@ -435,22 +640,112 @@ pa_init () static void opus_init () { - opus_int32 sampling_rate = 48000; - int channels = 1; int err; - frame_size = sampling_rate / 50; - pcm_length = frame_size * channels * sizeof (float); - enc = - opus_encoder_create (sampling_rate, channels, OPUS_APPLICATION_VOIP, - &err); - pcm_buffer = (float *) pa_xmalloc (pcm_length); - opus_data = (unsigned char *) calloc (max_payload_bytes, sizeof (char)); - - audio_message = pa_xmalloc (UINT16_MAX); - audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); + pcm_length = FRAME_SIZE * CHANNELS * sizeof (float); + pcm_buffer = pa_xmalloc (pcm_length); + opus_data = GNUNET_malloc (MAX_PAYLOAD_BYTES); + enc = opus_encoder_create (SAMPLING_RATE, + CHANNELS, + CONV_OPUS_APP_TYPE, + &err); + opus_encoder_ctl (enc, + OPUS_SET_PACKET_LOSS_PERC (CONV_OPUS_PACKET_LOSS_PERCENTAGE)); + opus_encoder_ctl (enc, + OPUS_SET_COMPLEXITY (CONV_OPUS_ENCODING_COMPLEXITY)); + opus_encoder_ctl (enc, + OPUS_SET_INBAND_FEC (CONV_OPUS_INBAND_FEC)); + opus_encoder_ctl (enc, + OPUS_SET_SIGNAL (OPUS_SIGNAL_VOICE)); } +static void +ogg_init () +{ + int serialno; + struct OpusHeadPacket headpacket; + struct OpusCommentsPacket *commentspacket; + size_t commentspacket_len; + + serialno = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, 0x7FFFFFFF); + + /*Initialize Ogg stream struct*/ + if (-1 == ogg_stream_init (&os, serialno)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("ogg_stream_init() failed.\n")); + exit (3); + } + + packet_id = 0; + + /*Write header*/ + { + ogg_packet op; + ogg_page og; + const char *opusver; + int vendor_length; + + memcpy (headpacket.magic, "OpusHead", 8); + headpacket.version = 1; + headpacket.channels = CHANNELS; + headpacket.preskip = GNUNET_htole16 (0); + headpacket.sampling_rate = GNUNET_htole32 (SAMPLING_RATE); + headpacket.gain = GNUNET_htole16 (0); + headpacket.channel_mapping = 0; /* Mono or stereo */ + + op.packet = (unsigned char *) &headpacket; + op.bytes = sizeof (headpacket); + op.b_o_s = 1; + op.e_o_s = 0; + op.granulepos = 0; + op.packetno = packet_id++; + ogg_stream_packetin (&os, &op); + + /* Head packet must be alone on its page */ + while (ogg_stream_flush (&os, &og)) + { + write_page (&og); + } + + commentspacket_len = sizeof (*commentspacket); + opusver = opus_get_version_string (); + vendor_length = strlen (opusver); + commentspacket_len += vendor_length; + commentspacket_len += sizeof (uint32_t); + + commentspacket = (struct OpusCommentsPacket *) malloc (commentspacket_len); + if (NULL == commentspacket) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to allocate %d bytes for second packet\n"), + commentspacket_len); + exit (5); + } + + memcpy (commentspacket->magic, "OpusTags", 8); + commentspacket->vendor_length = GNUNET_htole32 (vendor_length); + memcpy (&commentspacket[1], opusver, vendor_length); + *(uint32_t *) &((char *) &commentspacket[1])[vendor_length] = \ + GNUNET_htole32 (0); /* no tags */ + + op.packet = (unsigned char *) commentspacket; + op.bytes = commentspacket_len; + op.b_o_s = 0; + op.e_o_s = 0; + op.granulepos = 0; + op.packetno = packet_id++; + ogg_stream_packetin (&os, &op); + + /* Comment packets must not be mixed with audio packets on their pages */ + while (ogg_stream_flush (&os, &og)) + { + write_page (&og); + } + + free (commentspacket); + } +} /** * The main function for the record helper. @@ -466,8 +761,16 @@ main (int argc, char *argv[]) GNUNET_log_setup ("gnunet-helper-audio-record", "WARNING", NULL)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Audio source starts\n"); + audio_message = GNUNET_malloc (UINT16_MAX); + audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); + +#ifdef DEBUG_RECORD_PURE_OGG + dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0; +#endif + ogg_init (); opus_init (); pa_init (); - return 0; }