From 78f804db1c43f4195613b8dc176c0c472dc5d8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1?= =?UTF-8?q?=D1=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= Date: Mon, 13 Jan 2014 21:09:46 +0000 Subject: [PATCH] Implement ogg wrapping for the recorder --- src/conversation/gnunet-helper-audio-record.c | 268 +++++++++++++++++++++++--- 1 file changed, 245 insertions(+), 23 deletions(-) diff --git a/src/conversation/gnunet-helper-audio-record.c b/src/conversation/gnunet-helper-audio-record.c index 2081259..103881b 100644 --- a/src/conversation/gnunet-helper-audio-record.c +++ b/src/conversation/gnunet-helper-audio-record.c @@ -38,6 +38,7 @@ #include #include #include +#include #define SAMPLING_RATE 48000 @@ -51,6 +52,20 @@ static pa_sample_spec sample_spec = { .channels = 1 }; +typedef struct +{ + int version; + int channels; /* Number of channels: 1..255 */ + int preskip; + uint32_t input_sample_rate; + int gain; /* in dB S7.8 should be zero whenever possible */ + int channel_mapping; + /* The rest is only used if channel_mapping != 0 */ + int nb_streams; + int nb_coupled; + unsigned char stream_map[255]; +} OpusHeader; + /** * Pulseaudio mainloop api */ @@ -126,6 +141,20 @@ 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; /** * Pulseaudio shutdown task @@ -138,20 +167,54 @@ 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); + write_data ((const char *) audio_message, msg_size); +} + /** * Creates OPUS packets from PCM data */ static void packetizer () { - static unsigned long long toff; char *nbuf; size_t new_size; - const char *ptr; - size_t off; - ssize_t ret; - int len; // FIXME: int? - size_t msg_size; + int32_t len; + ogg_packet op; + ogg_page og; while (transmit_buffer_length >= transmit_buffer_index + pcm_length) { @@ -163,34 +226,51 @@ packetizer () 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); continue; } + /* As per OggOpus spec, granule is calculated as if the audio + had 48kHz sampling rate. */ + enc_granulepos += frame_size * 48000 / SAMPLING_RATE; - msg_size = sizeof (struct AudioMessage) + len; - audio_message->header.size = htons ((uint16_t) msg_size); - memcpy (&audio_message[1], opus_data, len); + /* FIXME: replace 255 * 255 with something else - this is not a + HARD limit, but a SOFT limit - page is committed when it goes + OVER that length in bytes. */ + while (ogg_stream_flush_fill (&os, &og, 255 * 255)) + { + if (og.header_len + og.body_len > UINT16_MAX - sizeof (struct AudioMessage)) + { + GNUNET_assert (0); + continue; + } + write_page (&og); + } - toff += msg_size; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Sending %u bytes of audio data (total: %llu)\n", - (unsigned int) msg_size, - toff); - ptr = (const char *) audio_message; - off = 0; - while (off < msg_size) + op.packet = (unsigned char *) opus_data; + op.bytes = len; + op.b_o_s = 0; + op.granulepos = enc_granulepos; + op.packetno = packet_id++; + ogg_stream_packetin (&os, &op); + + while (ogg_stream_flush_fill (&os, &og, 255 * 255)) { - ret = write (1, &ptr[off], msg_size - off); - if (0 >= ret) + if (og.header_len + og.body_len > UINT16_MAX - sizeof (struct AudioMessage)) { - if (-1 == ret) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "write"); - quit (2); + GNUNET_assert (0); + continue; } - off += ret; + write_page (&og); } } @@ -481,6 +561,147 @@ opus_init () OPUS_SET_SIGNAL (OPUS_SIGNAL_VOICE)); } +static int +write_uint32 (unsigned char *data, int maxlen, int *pos, ogg_uint32_t val) +{ + if (*pos > maxlen - 4) + return 0; + data[*pos + 0] = (val >> 0) & 0xFF; + data[*pos + 1] = (val >> 8) & 0xFF; + data[*pos + 2] = (val >> 16) & 0xFF; + data[*pos + 3] = (val >> 24) & 0xFF; + *pos += 4; + return 1; +} + +static int +write_uint16 (unsigned char *data, int maxlen, int *pos, ogg_uint16_t val) +{ + if (*pos > maxlen - 2) + return 0; + data[*pos + 0] = (val >> 0) & 0xFF; + data[*pos + 1] = (val >> 8) & 0xFF; + *pos += 2; + return 1; +} + +static int +write_chars (unsigned char *data, int maxlen, int *pos, const unsigned char *str, int nb_chars) +{ + int i; + if (*pos > maxlen - nb_chars) + return 0; + for (i = 0; i < nb_chars; i++) + data[(*pos)++] = str[i]; + return 1; +} + +static int +opus_header_to_packet (const OpusHeader *h, unsigned char *packet, int len) +{ + int i; + int pos; + unsigned char ch; + + pos = 0; + if (len < 19) + return 0; + if (!write_chars (packet, len, &pos, (const unsigned char*)"OpusHead", 8)) + return 0; + /* Version is 1 */ + ch = 1; + if (!write_chars (packet, len, &pos, &ch, 1)) + return 0; + + ch = h->channels; + if (!write_chars (packet, len, &pos, &ch, 1)) + return 0; + + if (!write_uint16 (packet, len, &pos, h->preskip)) + return 0; + + if (!write_uint32 (packet, len, &pos, h->input_sample_rate)) + return 0; + + if (!write_uint16 (packet, len, &pos, h->gain)) + return 0; + + ch = h->channel_mapping; + if (!write_chars (packet, len, &pos, &ch, 1)) + return 0; + + if (h->channel_mapping != 0) + { + ch = h->nb_streams; + if (!write_chars (packet, len, &pos, &ch, 1)) + return 0; + + ch = h->nb_coupled; + if (!write_chars (packet, len, &pos, &ch, 1)) + return 0; + + /* Multi-stream support */ + for (i = 0; i < h->channels; i++) + { + if (!write_chars (packet, len, &pos, &h->stream_map[i], 1)) + return 0; + } + } + + return pos; +} + + +static void +ogg_init () +{ + int serialno; + OpusHeader header; + int channels = 1; + + serialno = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, 0xFFFFFFFF); + + /* OggOpus headers */ + header.channels = channels; + header.channel_mapping = 255; + if (header.channels <= 8) + header.channel_mapping = header.channels > 2; + header.input_sample_rate = SAMPLING_RATE; + header.gain = 0; + header.preskip = 0; + + /*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; + unsigned char header_data[100]; + int packet_size = opus_header_to_packet (&header, header_data, 100); + op.packet = header_data; + op.bytes = packet_size; + op.b_o_s = 1; + op.e_o_s = 0; + op.granulepos = 0; + op.packetno = packet_id++; + ogg_stream_packetin (&os, &op); + + while (ogg_stream_flush (&os, &og)) + { + write_page (&og); + } + } + +} + /** * The main function for the record helper. @@ -500,6 +721,7 @@ main (int argc, char *argv[]) "Audio source starts\n"); audio_message = GNUNET_malloc (UINT16_MAX); audio_message->header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); + ogg_init (); opus_init (); pa_init (); return 0; -- 1.8.4