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?= <lrn1986@gmail.com>
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 <pulse/pulseaudio.h>
 #include <opus/opus.h>
 #include <opus/opus_types.h>
+#include <ogg/ogg.h>
 
 #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

