[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[GNUnet-SVN] r31940 - gnunet/src/conversation
From: |
gnunet |
Subject: |
[GNUnet-SVN] r31940 - gnunet/src/conversation |
Date: |
Fri, 17 Jan 2014 05:12:51 +0100 |
Author: LRN
Date: 2014-01-17 05:12:51 +0100 (Fri, 17 Jan 2014)
New Revision: 31940
Modified:
gnunet/src/conversation/Makefile.am
gnunet/src/conversation/gnunet-helper-audio-playback-gst.c
gnunet/src/conversation/gnunet-helper-audio-playback.c
gnunet/src/conversation/gnunet-helper-audio-record-gst.c
gnunet/src/conversation/gnunet-helper-audio-record.c
Log:
Wrap opus in ogg container
Modified: gnunet/src/conversation/Makefile.am
===================================================================
--- gnunet/src/conversation/Makefile.am 2014-01-16 23:38:03 UTC (rev 31939)
+++ gnunet/src/conversation/Makefile.am 2014-01-17 04:12:51 UTC (rev 31940)
@@ -101,7 +101,7 @@
gnunet-helper-audio-record.c
gnunet_helper_audio_record_LDADD = \
$(top_builddir)/src/util/libgnunetutil.la \
- -lpulse -lopus\
+ -lpulse -lopus -logg \
$(INTLLIBS)
gnunet_helper_audio_record_LDFLAGS = \
$(GNUNET_LDFLAGS) $(WINFLAGS)
@@ -110,7 +110,7 @@
gnunet-helper-audio-playback.c
gnunet_helper_audio_playback_LDADD = \
$(top_builddir)/src/util/libgnunetutil.la \
- -lpulse -lopus\
+ -lpulse -lopus -logg \
$(INTLLIBS)
gnunet_helper_audio_playback_LDFLAGS = \
$(GNUNET_LDFLAGS) $(WINFLAGS)
@@ -131,7 +131,6 @@
gnunet-helper-audio-playback-gst.c
gnunet_helper_audio_playback_LDADD = \
$(top_builddir)/src/util/libgnunetutil.la \
- -lopus \
$(GST_LIBS) \
$(INTLLIBS)
gnunet_helper_audio_playback_LDFLAGS = \
Modified: gnunet/src/conversation/gnunet-helper-audio-playback-gst.c
===================================================================
--- gnunet/src/conversation/gnunet-helper-audio-playback-gst.c 2014-01-16
23:38:03 UTC (rev 31939)
+++ gnunet/src/conversation/gnunet-helper-audio-playback-gst.c 2014-01-17
04:12:51 UTC (rev 31940)
@@ -30,26 +30,17 @@
#include "gnunet_core_service.h"
#include <gst/gst.h>
+#include <gst/audio/gstaudiobasesrc.h>
#include <gst/app/gstappsrc.h>
-#include <gst/audio/gstaudiobasesrc.h>
#include <glib.h>
-#include <opus/opus.h>
-#include <opus/opus_types.h>
+#define DEBUG_READ_PURE_OGG 1
/**
* How much data to read in one go
*/
#define MAXLINE 4096
-#define SAMPLING_RATE 48000
-
-#define CHANNELS 1
-
-#define FRAME_SIZE (SAMPLING_RATE / 50)
-
-#define PCM_LENGTH (FRAME_SIZE * CHANNELS * sizeof (int16_t))
-
/**
* Max number of microseconds to buffer in audiosink.
* Default is 200000
@@ -77,31 +68,18 @@
*/
static GstElement *source;
-/**
- * OPUS decoder
- */
-static OpusDecoder *dec;
+static GstElement *demuxer;
+static GstElement *decoder;
+static GstElement *conv;
+static GstElement *resampler;
+static GstElement *sink;
-
/**
* Set to 1 to break the reading loop
*/
static int abort_read;
-
-/**
- * OPUS initialization
- */
static void
-opus_init ()
-{
- int err;
- int channels = 1;
-
- dec = opus_decoder_create (SAMPLING_RATE, channels, &err);
-}
-
-void
sink_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name,
gpointer user_data)
{
if (GST_IS_AUDIO_BASE_SRC (object))
@@ -109,6 +87,22 @@
}
static void
+ogg_pad_added (GstElement *element, GstPad *pad, gpointer data)
+{
+ GstPad *sinkpad;
+ GstElement *decoder = (GstElement *) data;
+
+ /* We can now link this pad with the opus-decoder sink pad */
+ sinkpad = gst_element_get_static_pad (decoder, "sink");
+
+ gst_pad_link (pad, sinkpad);
+
+ gst_element_link_many (decoder, conv, resampler, sink, NULL);
+
+ gst_object_unref (sinkpad);
+}
+
+static void
quit ()
{
if (NULL != source)
@@ -157,7 +151,51 @@
quit ();
}
+static int
+feed_buffer_to_gst (const char *audio, size_t b_len)
+{
+ GstBuffer *b;
+ gchar *bufspace;
+ GstFlowReturn flow;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Feeding %u bytes to GStreamer\n",
+ (unsigned int) b_len);
+
+ bufspace = g_memdup (audio, b_len);
+ b = gst_buffer_new_wrapped (bufspace, b_len);
+ if (NULL == b)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to wrap a buffer\n");
+ g_free (bufspace);
+ return GNUNET_SYSERR;
+ }
+ flow = gst_app_src_push_buffer (GST_APP_SRC (source), b);
+ /* They all return GNUNET_OK, because currently player stops when
+ * data stops coming. This might need to be changed for the player
+ * to also stop when pipeline breaks.
+ */
+ switch (flow)
+ {
+ case GST_FLOW_OK:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Fed %u bytes to the pipeline\n",
+ (unsigned int) b_len);
+ break;
+ case GST_FLOW_FLUSHING:
+ /* buffer was dropped, because pipeline state is not PAUSED or PLAYING */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Dropped a buffer\n");
+ break;
+ case GST_FLOW_EOS:
+ /* end of stream */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "EOS\n");
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unexpected push result\n");
+ break;
+ }
+ return GNUNET_OK;
+}
+
/**
* Message callback
*/
@@ -167,68 +205,15 @@
const struct GNUNET_MessageHeader *msg)
{
struct AudioMessage *audio;
- GstBuffer *b;
- int16_t *bufspace;
- GstFlowReturn flow;
- int ret;
+ size_t b_len;
switch (ntohs (msg->type))
{
case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
audio = (struct AudioMessage *) msg;
- bufspace = (int16_t *) g_malloc (PCM_LENGTH);
-
- ret = opus_decode (dec,
- (const unsigned char *) &audio[1],
- ntohs (audio->header.size) - sizeof (struct
AudioMessage),
- bufspace,
- FRAME_SIZE, 0);
- if (ret < 0)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Opus decoding failed: %d\n",
- ret);
- g_free (bufspace);
- return GNUNET_OK;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Decoded frame with %u bytes\n",
- ntohs (audio->header.size));
-
- b = gst_buffer_new_wrapped (bufspace, ret * sizeof (int16_t));
- if (NULL == b)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to wrap a buffer\n");
- g_free (bufspace);
- return GNUNET_SYSERR;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pushing...\n");
- flow = gst_app_src_push_buffer (GST_APP_SRC (source), b);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pushed!\n");
- /* They all return GNUNET_OK, because currently player stops when
- * data stops coming. This might need to be changed for the player
- * to also stop when pipeline breaks.
- */
- switch (flow)
- {
- case GST_FLOW_OK:
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Fed %u bytes to the pipeline\n",
- (unsigned int) ret * sizeof (int16_t));
- break;
- case GST_FLOW_FLUSHING:
- /* buffer was dropped, because pipeline state is not PAUSED or PLAYING */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Dropped a buffer\n");
- break;
- case GST_FLOW_EOS:
- /* end of stream */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO, "EOS\n");
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unexpected push result\n");
- break;
- }
+ b_len = ntohs (audio->header.size) - sizeof (struct AudioMessage);
+ feed_buffer_to_gst ((const char *) &audio[1], b_len);
break;
default:
break;
@@ -240,15 +225,16 @@
int
main (int argc, char **argv)
{
- GstElement *conv, *resampler, *sink;
GstBus *bus;
- GstCaps *caps;
guint bus_watch_id;
uint64_t toff;
typedef void (*SignalHandlerPointer) (int);
SignalHandlerPointer inthandler, termhandler;
+#ifdef DEBUG_READ_PURE_OGG
+ int read_pure_ogg = getenv ("GNUNET_READ_PURE_OGG") ? 1 : 0;
+#endif
inthandler = signal (SIGINT, signalhandler);
termhandler = signal (SIGTERM, signalhandler);
@@ -257,8 +243,6 @@
setmode (0, _O_BINARY);
#endif
- opus_init ();
-
/* Initialisation */
gst_init (&argc, &argv);
@@ -275,11 +259,13 @@
/* Create gstreamer elements */
pipeline = gst_pipeline_new ("audio-player");
source = gst_element_factory_make ("appsrc", "audio-input");
+ demuxer = gst_element_factory_make ("oggdemux", "ogg-demuxer");
+ decoder = gst_element_factory_make ("opusdec", "opus-decoder");
conv = gst_element_factory_make ("audioconvert", "converter");
resampler= gst_element_factory_make ("audioresample", "resampler");
sink = gst_element_factory_make ("autoaudiosink", "audiosink");
- if (!pipeline || !source || !conv || !resampler || !sink)
+ if (!pipeline || !source || !conv || !resampler || !decoder || !demuxer ||
!sink)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"One element could not be created. Exiting.\n");
@@ -287,16 +273,8 @@
}
g_signal_connect (sink, "child-added", G_CALLBACK (sink_child_added), NULL);
+ g_signal_connect (demuxer, "pad-added", G_CALLBACK (ogg_pad_added), decoder);
- caps = gst_caps_new_simple ("audio/x-raw",
- "format", G_TYPE_STRING, "S16LE",
- "rate", G_TYPE_INT, SAMPLING_RATE,
- "channels", G_TYPE_INT, CHANNELS,
- "layout", G_TYPE_STRING, "interleaved",
- NULL);
- gst_app_src_set_caps (GST_APP_SRC (source), caps);
- gst_caps_unref (caps);
-
/* Keep a reference to it, we operate on it */
gst_object_ref (GST_OBJECT (source));
@@ -304,23 +282,29 @@
/* we feed appsrc as fast as possible, it just blocks when it's full */
g_object_set (G_OBJECT (source),
- "format", GST_FORMAT_TIME,
+/* "format", GST_FORMAT_TIME,*/
"block", TRUE,
"is-live", TRUE,
NULL);
+ g_object_set (G_OBJECT (decoder),
+/* "plc", FALSE,*/
+/* "apply-gain", TRUE,*/
+ "use-inband-fec", TRUE,
+ NULL);
+
/* we add a message handler */
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline);
gst_object_unref (bus);
/* we add all elements into the pipeline */
- /* audio-input | converter | resampler | audiosink */
- gst_bin_add_many (GST_BIN (pipeline), source, conv,
+ /* audio-input | ogg-demuxer | opus-decoder | converter | resampler |
audiosink */
+ gst_bin_add_many (GST_BIN (pipeline), source, demuxer, decoder, conv,
resampler, sink, NULL);
/* we link the elements together */
- gst_element_link_many (source, conv, resampler, sink, NULL);
+ gst_element_link_many (source, demuxer, NULL);
/* Set the pipeline to "playing" state*/
GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n");
@@ -349,6 +333,13 @@
toff);
if (0 == ret)
break;
+#ifdef DEBUG_READ_PURE_OGG
+ if (read_pure_ogg)
+ {
+ feed_buffer_to_gst (readbuf, ret);
+ }
+ else
+#endif
GNUNET_SERVER_mst_receive (stdin_mst, NULL,
readbuf, ret,
GNUNET_NO, GNUNET_NO);
Modified: gnunet/src/conversation/gnunet-helper-audio-playback.c
===================================================================
--- gnunet/src/conversation/gnunet-helper-audio-playback.c 2014-01-16
23:38:03 UTC (rev 31939)
+++ gnunet/src/conversation/gnunet-helper-audio-playback.c 2014-01-17
04:12:51 UTC (rev 31940)
@@ -38,20 +38,32 @@
#include <pulse/pulseaudio.h>
#include <opus/opus.h>
#include <opus/opus_types.h>
+#include <ogg/ogg.h>
+#define DEBUG_READ_PURE_OGG 1
+#define DEBUG_DUMP_DECODED_OGG 1
+
#define MAXLINE 4096
#define SAMPLING_RATE 48000
+#define CHANNELS 1
+
+/* 120ms at 48000 */
+#define MAX_FRAME_SIZE (960 * 6)
+
/**
* Pulseaudio specification. May change in the future.
*/
static pa_sample_spec sample_spec = {
.format = PA_SAMPLE_FLOAT32LE,
.rate = SAMPLING_RATE,
- .channels = 1
+ .channels = CHANNELS
};
+#ifdef DEBUG_DUMP_DECODED_OGG
+static int dump_to_stdout;
+#endif
/**
* Pulseaudio mainloop api
@@ -84,11 +96,6 @@
static float *pcm_buffer;
/**
- * Length of PCM buffer
- */
-static int pcm_length;
-
-/**
* Number of samples for one frame
*/
static int frame_size;
@@ -99,53 +106,217 @@
static int ready_pipe[2];
/**
- * Message callback
+ * Ogg I/O state.
*/
-static int
-stdin_receiver (void *cls,
- void *client,
- const struct GNUNET_MessageHeader *msg)
+static ogg_sync_state oy;
+
+/**
+ * Ogg stream state.
+ */
+static ogg_stream_state os;
+
+static int channels;
+
+static int preskip;
+
+static float gain;
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/* OggOpus spec says the numbers must be in little-endian order */
+struct OpusHeadPacket
{
- struct AudioMessage *audio;
- int ret;
+ 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;
+};
- switch (ntohs (msg->type))
+GNUNET_NETWORK_STRUCT_END
+
+/*Process an Opus header and setup the opus decoder based on it.
+ It takes several pointers for header values which are needed
+ elsewhere in the code.*/
+static OpusDecoder *
+process_header (ogg_packet *op)
+{
+ int err;
+ OpusDecoder *dec;
+ struct OpusHeadPacket header;
+
+ if (op->bytes < sizeof (header))
+ return NULL;
+ memcpy (&header, op->packet, sizeof (header));
+ header.preskip = GNUNET_le16toh (header.preskip);
+ header.sampling_rate = GNUNET_le32toh (header.sampling_rate);
+ header.gain = GNUNET_le16toh (header.gain);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Header: v%u, %u-ch, skip %u, %uHz, %u gain\n",
+ header.version, header.channels, header.preskip,
header.sampling_rate, header.gain);
+
+ channels = header.channels;
+ preskip = header.preskip;
+
+ if (header.channel_mapping != 0)
{
- case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
- audio = (struct AudioMessage *) msg;
+ fprintf (stderr, "This implementation does not support non-mono
streams\n");
+ return NULL;
+ }
- ret = opus_decode_float (dec,
- (const unsigned char *) &audio[1],
- ntohs (audio->header.size) - sizeof (struct
AudioMessage),
- pcm_buffer,
- frame_size, 0);
- if (ret < 0)
+ dec = opus_decoder_create (SAMPLING_RATE, channels, &err);
+ if (OPUS_OK != err)
+ {
+ fprintf (stderr, "Cannot create encoder: %s\n", opus_strerror (err));
+ return NULL;
+ }
+ if (!dec)
+ {
+ fprintf (stderr, "Decoder initialization failed: %s\n", opus_strerror
(err));
+ return NULL;
+ }
+
+ if (0 != header.gain)
+ {
+ /*Gain API added in a newer libopus version, if we don't have it
+ we apply the gain ourselves. We also add in a user provided
+ manual gain at the same time.*/
+ int gainadj = (int) header.gain;
+ err = opus_decoder_ctl (dec, OPUS_SET_GAIN (gainadj));
+ if(OPUS_UNIMPLEMENTED == err)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Opus decoding failed: %d\n",
- ret);
- return GNUNET_OK;
+ gain = pow (10.0, gainadj / 5120.0);
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Decoded frame with %u bytes\n",
- ntohs (audio->header.size));
- if (pa_stream_write
- (stream_out, pcm_buffer, pcm_length, NULL, 0,
- PA_SEEK_RELATIVE) < 0)
+ else if (OPUS_OK != err)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- _("pa_stream_write() failed: %s\n"),
- pa_strerror (pa_context_errno (context)));
- return GNUNET_OK;
+ fprintf (stderr, "Error setting gain: %s\n", opus_strerror (err));
+ return NULL;
}
- break;
- default:
- break;
}
- return GNUNET_OK;
+
+ return dec;
}
+#ifdef DEBUG_DUMP_DECODED_OGG
+static size_t fwrite_le32(opus_int32 i32, FILE *file)
+{
+ unsigned char buf[4];
+ buf[0]=(unsigned char)(i32&0xFF);
+ buf[1]=(unsigned char)(i32>>8&0xFF);
+ buf[2]=(unsigned char)(i32>>16&0xFF);
+ buf[3]=(unsigned char)(i32>>24&0xFF);
+ return fwrite(buf,4,1,file);
+}
+
+static size_t fwrite_le16(int i16, FILE *file)
+{
+ unsigned char buf[2];
+ buf[0]=(unsigned char)(i16&0xFF);
+ buf[1]=(unsigned char)(i16>>8&0xFF);
+ return fwrite(buf,2,1,file);
+}
+
+static int write_wav_header()
+{
+ int ret;
+ FILE *file = stdout;
+
+ ret = fprintf (file, "RIFF") >= 0;
+ ret &= fwrite_le32 (0x7fffffff, file);
+
+ ret &= fprintf (file, "WAVEfmt ") >= 0;
+ ret &= fwrite_le32 (16, file);
+ ret &= fwrite_le16 (1, file);
+ ret &= fwrite_le16 (channels, file);
+ ret &= fwrite_le32 (SAMPLING_RATE, file);
+ ret &= fwrite_le32 (2*channels*SAMPLING_RATE, file);
+ ret &= fwrite_le16 (2*channels, file);
+ ret &= fwrite_le16 (16, file);
+
+ ret &= fprintf (file, "data") >= 0;
+ ret &= fwrite_le32 (0x7fffffff, file);
+
+ return !ret ? -1 : 16;
+}
+
+#endif
+
+static int64_t
+audio_write (int64_t maxout)
+{
+ int64_t sampout = 0;
+ int tmp_skip;
+ unsigned out_len;
+ unsigned to_write;
+ float *output;
+#ifdef DEBUG_DUMP_DECODED_OGG
+ static int wrote_wav_header;
+
+ if (dump_to_stdout && !wrote_wav_header)
+ {
+ write_wav_header ();
+ wrote_wav_header = 1;
+ }
+#endif
+ maxout = 0 > maxout ? 0 : maxout;
+ do
+ {
+ tmp_skip = (preskip > frame_size) ? (int) frame_size : preskip;
+ preskip -= tmp_skip;
+ output = pcm_buffer + channels * tmp_skip;
+ out_len = frame_size - tmp_skip;
+ if (out_len > MAX_FRAME_SIZE)
+ exit (6);
+ frame_size = 0;
+
+ to_write = out_len < maxout ? out_len : (unsigned) maxout;
+ if (0 < maxout)
+ {
+ int64_t wrote = 0;
+ wrote = to_write;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Writing %u * %u * %u = %u bytes into PA\n",
+ to_write, channels, sizeof (float),
+ to_write * channels * sizeof (float));
+#ifdef DEBUG_DUMP_DECODED_OGG
+ if (dump_to_stdout)
+ {
+# define fminf(_x,_y) ((_x)<(_y)?(_x):(_y))
+# define fmaxf(_x,_y) ((_x)>(_y)?(_x):(_y))
+# define float2int(flt) ((int)(floor(.5+flt)))
+ int i;
+ int16_t *out = alloca(sizeof(short)*MAX_FRAME_SIZE*channels);
+ for (i=0;i<(int)out_len*channels;i++)
+
out[i]=(short)float2int(fmaxf(-32768,fminf(output[i]*32768.f,32767)));
+
+ fwrite (out, 2 * channels, out_len<maxout?out_len:maxout, stdout);
+ }
+ else
+#endif
+ if (pa_stream_write
+ (stream_out, output, to_write * channels * sizeof (float), NULL, 0,
+ PA_SEEK_RELATIVE) < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("pa_stream_write() failed: %s\n"),
+ pa_strerror (pa_context_errno (context)));
+ }
+ sampout += wrote;
+ maxout -= wrote;
+ }
+ } while (0 < frame_size && 0 < maxout);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Wrote %" PRId64 " samples\n",
+ sampout);
+ return sampout;
+}
+
+
/**
* Pulseaudio shutdown task
*/
@@ -157,7 +328,237 @@
}
+static void
+ogg_demux_and_decode ()
+{
+ ogg_page og;
+ static int stream_init;
+ int64_t page_granule = 0;
+ ogg_packet op;
+ static int has_opus_stream;
+ static int has_tags_packet;
+ static int32_t opus_serialno;
+ static int64_t link_out;
+ static int64_t packet_count;
+ int eos = 0;
+ static int total_links;
+ static int gran_offset;
+
+ while (1 == ogg_sync_pageout (&oy, &og))
+ {
+ if (0 == stream_init)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Initialized the stream\n");
+ ogg_stream_init (&os, ogg_page_serialno (&og));
+ stream_init = 1;
+ }
+ if (ogg_page_serialno (&og) != os.serialno)
+ {
+ /* so all streams are read. */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Re-set serial number\n");
+ ogg_stream_reset_serialno (&os, ogg_page_serialno (&og));
+ }
+ /*Add page to the bitstream*/
+ ogg_stream_pagein (&os, &og);
+ page_granule = ogg_page_granulepos (&og);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Reading page that ends at %" PRId64 "\n",
+ page_granule);
+ /*Extract all available packets*/
+ while (1 == ogg_stream_packetout (&os, &op))
+ {
+ /*OggOpus streams are identified by a magic string in the initial
+ stream header.*/
+ if (op.b_o_s && op.bytes >= 8 && !memcmp (op.packet, "OpusHead", 8))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got Opus Header\n");
+ if (has_opus_stream && has_tags_packet)
+ {
+ /*If we're seeing another BOS OpusHead now it means
+ the stream is chained without an EOS.
+ This can easily happen if record helper is terminated unexpectedly.
+ */
+ has_opus_stream = 0;
+ if (dec)
+ opus_decoder_destroy (dec);
+ dec = NULL;
+ fprintf (stderr, "\nWarning: stream %" PRId64 " ended without EOS
and a new stream began.\n", (int64_t) os.serialno);
+ }
+ if (!has_opus_stream)
+ {
+ if (packet_count > 0 && opus_serialno == os.serialno)
+ {
+ fprintf (stderr, "\nError: Apparent chaining without changing
serial number (%" PRId64 "==%" PRId64 ").\n",
+ (int64_t) opus_serialno, (int64_t) os.serialno);
+ quit(1);
+ }
+ opus_serialno = os.serialno;
+ has_opus_stream = 1;
+ has_tags_packet = 0;
+ link_out = 0;
+ packet_count = 0;
+ eos = 0;
+ total_links++;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got header for stream %" PRId64 ", this is %dth link\n",
+ (int64_t) opus_serialno, total_links);
+ }
+ else
+ {
+ fprintf (stderr, "\nWarning: ignoring opus stream %" PRId64 "\n",
(int64_t) os.serialno);
+ }
+ }
+ if (!has_opus_stream || os.serialno != opus_serialno)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "breaking out\n");
+ break;
+ }
+ /*If first packet in a logical stream, process the Opus header*/
+ if (0 == packet_count)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Decoding header\n");
+ dec = process_header (&op);
+ if (!dec)
+ quit (1);
+
+ if (0 != ogg_stream_packetout (&os, &op) || 255 ==
og.header[og.header_len - 1])
+ {
+ /*The format specifies that the initial header and tags packets are
on their
+ own pages. To aid implementors in discovering that their files are
wrong
+ we reject them explicitly here. In some player designs files like
this would
+ fail even without an explicit test.*/
+ fprintf (stderr, "Extra packets on initial header page. Invalid
stream.\n");
+ quit (1);
+ }
+
+ /*Remember how many samples at the front we were told to skip
+ so that we can adjust the timestamp counting.*/
+ gran_offset = preskip;
+
+ if (!pcm_buffer)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Allocating %u * %u * %u = %u bytes of buffer space\n",
+ MAX_FRAME_SIZE, channels, sizeof (float),
+ MAX_FRAME_SIZE * channels * sizeof (float));
+ pcm_buffer = pa_xmalloc (sizeof (float) * MAX_FRAME_SIZE * channels);
+ }
+ }
+ else if (1 == packet_count)
+ {
+ has_tags_packet = 1;
+ if (0 != ogg_stream_packetout (&os, &op) || 255 ==
og.header[og.header_len - 1])
+ {
+ fprintf (stderr, "Extra packets on initial tags page. Invalid
stream.\n");
+ quit (1);
+ }
+ }
+ else
+ {
+ int ret;
+ int64_t maxout;
+ int64_t outsamp;
+
+ /*End of stream condition*/
+ if (op.e_o_s && os.serialno == opus_serialno)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got EOS\n");
+ eos = 1; /* don't care for anything except opus eos */
+ }
+
+ /*Decode Opus packet*/
+ ret = opus_decode_float (dec,
+ (const unsigned char *) op.packet,
+ op.bytes,
+ pcm_buffer,
+ MAX_FRAME_SIZE, 0);
+
+ /*If the decoder returned less than zero, we have an error.*/
+ if (0 > ret)
+ {
+ fprintf (stderr, "Decoding error: %s\n", opus_strerror (ret));
+ break;
+ }
+ frame_size = ret;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Decoded %d bytes/channel (%d bytes) from %u compressed
bytes\n",
+ ret, ret * channels, op.bytes);
+
+ /*Apply header gain, if we're not using an opus library new
+ enough to do this internally.*/
+ if (0 != gain)
+ {
+ int i;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Applying gain %f\n",
+ gain);
+ for (i = 0; i < frame_size * channels; i++)
+ pcm_buffer[i] *= gain;
+ }
+
+ /*This handles making sure that our output duration respects
+ the final end-trim by not letting the output sample count
+ get ahead of the granpos indicated value.*/
+ maxout = ((page_granule - gran_offset) * SAMPLING_RATE / 48000) -
link_out;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Writing audio packet %" PRId64 ", at most %" PRId64 "
samples\n",
+ packet_count, maxout);
+
+ outsamp = audio_write (0 > maxout ? 0 : maxout);
+ link_out += outsamp;
+ }
+ packet_count++;
+ }
+ if (eos)
+ {
+ has_opus_stream = 0;
+ if (dec)
+ opus_decoder_destroy (dec);
+ dec = NULL;
+ }
+ }
+}
+
/**
+ * Message callback
+ */
+static int
+stdin_receiver (void *cls,
+ void *client,
+ const struct GNUNET_MessageHeader *msg)
+{
+ struct AudioMessage *audio;
+ char *data;
+ size_t payload_len;
+
+ switch (ntohs (msg->type))
+ {
+ case GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO:
+ audio = (struct AudioMessage *) msg;
+ payload_len = ntohs (audio->header.size) - sizeof (struct AudioMessage);
+
+ /*Get the ogg buffer for writing*/
+ data = ogg_sync_buffer (&oy, payload_len);
+ /*Read bitstream from input file*/
+ memcpy (data, (const unsigned char *) &audio[1], payload_len);
+ ogg_sync_wrote (&oy, payload_len);
+
+ ogg_demux_and_decode ();
+ break;
+ default:
+ break;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Callback when data is there for playback
*/
static void
@@ -299,23 +700,18 @@
}
-/**
- * OPUS initialization
- */
static void
-opus_init ()
+ogg_init ()
{
- int err;
- int channels = 1;
+ ogg_sync_init (&oy);
+}
- frame_size = SAMPLING_RATE / 50;
- pcm_length = frame_size * channels * sizeof (float);
-
- dec = opus_decoder_create (SAMPLING_RATE, channels, &err);
- pcm_buffer = (float *) pa_xmalloc (frame_size * channels * sizeof (float));
+static void
+drain_callback (pa_stream*s, int success, void *userdata)
+{
+ pa_threaded_mainloop_signal (m, 0);
}
-
/**
* The main function for the playback helper.
*
@@ -332,6 +728,9 @@
struct GNUNET_SERVER_MessageStreamTokenizer *stdin_mst;
char c;
ssize_t ret;
+#ifdef DEBUG_READ_PURE_OGG
+ int read_pure_ogg = getenv ("GNUNET_READ_PURE_OGG") ? 1 : 0;
+#endif
GNUNET_assert (GNUNET_OK ==
GNUNET_log_setup ("gnunet-helper-audio-playback",
@@ -343,7 +742,7 @@
return 1;
}
stdin_mst = GNUNET_SERVER_mst_create (&stdin_receiver, NULL);
- opus_init ();
+ ogg_init ();
pa_init ();
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Waiting for PulseAudio to be ready.\n");
@@ -352,6 +751,9 @@
close (ready_pipe[1]);
ready_pipe[0] = -1;
ready_pipe[1] = -1;
+#ifdef DEBUG_DUMP_DECODED_OGG
+ dump_to_stdout = getenv ("GNUNET_DUMP_DECODED_OGG") ? 1 : 0;
+#endif
while (1)
{
ret = read (0, readbuf, sizeof (readbuf));
@@ -369,10 +771,41 @@
}
if (0 == ret)
break;
+#ifdef DEBUG_READ_PURE_OGG
+ if (read_pure_ogg)
+ {
+ char *data = ogg_sync_buffer (&oy, ret);
+ memcpy (data, readbuf, ret);
+ ogg_sync_wrote (&oy, ret);
+ ogg_demux_and_decode ();
+ }
+ else
+#endif
GNUNET_SERVER_mst_receive (stdin_mst, NULL,
readbuf, ret,
GNUNET_NO, GNUNET_NO);
}
GNUNET_SERVER_mst_destroy (stdin_mst);
+ if (stream_out)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Locking\n");
+ pa_threaded_mainloop_lock (m);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Draining\n");
+ pa_operation *o = pa_stream_drain (stream_out, drain_callback, NULL);
+ while (pa_operation_get_state (o) == PA_OPERATION_RUNNING)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Waiting\n");
+ pa_threaded_mainloop_wait (m);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Unreffing\n");
+ pa_operation_unref (o);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Unlocking\n");
+ pa_threaded_mainloop_unlock (m);
+ }
return 0;
}
Modified: gnunet/src/conversation/gnunet-helper-audio-record-gst.c
===================================================================
--- gnunet/src/conversation/gnunet-helper-audio-record-gst.c 2014-01-16
23:38:03 UTC (rev 31939)
+++ gnunet/src/conversation/gnunet-helper-audio-record-gst.c 2014-01-17
04:12:51 UTC (rev 31940)
@@ -34,6 +34,8 @@
#include <gst/audio/gstaudiobasesrc.h>
#include <glib.h>
+#define DEBUG_RECORD_PURE_OGG 1
+
/**
* Number of channels.
* Must be one of the following (from libopusenc documentation):
@@ -51,7 +53,7 @@
* Must be one of the following (from libopus documentation):
* 2.5, 5, 10, 20, 40 or 60
*/
-#define OPUS_FRAME_SIZE 20
+#define OPUS_FRAME_SIZE 40
/**
* Expected packet loss to prepare for, in percents.
@@ -68,19 +70,37 @@
* Max number of microseconds to buffer in audiosource.
* Default is 200000
*/
-#define BUFFER_TIME 1000
+#define BUFFER_TIME 1000 /* 1ms */
/**
* Min number of microseconds to buffer in audiosource.
* Default is 10000
*/
-#define LATENCY_TIME 1000
+#define LATENCY_TIME 1000 /* 1ms */
/**
+ * Maximum delay in multiplexing streams, in ns.
+ * Setting this to 0 forces page flushing, which
+ * decreases delay, but increases overhead.
+ */
+#define OGG_MAX_DELAY 0
+
+/**
+ * Maximum delay for sending out a page, in ns.
+ * Setting this to 0 forces page flushing, which
+ * decreases delay, but increases overhead.
+ */
+#define OGG_MAX_PAGE_DELAY 0
+
+/**
* Main pipeline.
*/
static GstElement *pipeline;
+#ifdef DEBUG_RECORD_PURE_OGG
+static int dump_pure_ogg;
+#endif
+
static void
quit ()
{
@@ -103,13 +123,13 @@
{
gchar *debug;
GError *error;
-
+
gst_message_parse_error (msg, &error, &debug);
g_free (debug);
-
+
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: %s\n", error->message);
g_error_free (error);
-
+
quit ();
break;
}
@@ -137,18 +157,23 @@
int
main (int argc, char **argv)
{
- GstElement *source, *encoder, *conv, *resampler, *sink;
+ GstElement *source, *filter, *encoder, *conv, *resampler, *sink, *oggmux;
+ GstCaps *caps;
GstBus *bus;
guint bus_watch_id;
struct AudioMessage audio_message;
int abort_send = 0;
typedef void (*SignalHandlerPointer) (int);
-
+
SignalHandlerPointer inthandler, termhandler;
inthandler = signal (SIGINT, signalhandler);
termhandler = signal (SIGTERM, signalhandler);
+#ifdef DEBUG_RECORD_PURE_OGG
+ dump_pure_ogg = getenv ("GNUNET_RECORD_PURE_OGG") ? 1 : 0;
+#endif
+
#ifdef WINDOWS
setmode (1, _O_BINARY);
#endif
@@ -169,12 +194,14 @@
/* Create gstreamer elements */
pipeline = gst_pipeline_new ("audio-recorder");
source = gst_element_factory_make ("autoaudiosrc", "audiosource");
+ filter = gst_element_factory_make ("capsfilter", "filter");
conv = gst_element_factory_make ("audioconvert", "converter");
resampler= gst_element_factory_make ("audioresample", "resampler");
encoder = gst_element_factory_make ("opusenc", "opus-encoder");
+ oggmux = gst_element_factory_make ("oggmux", "ogg-muxer");
sink = gst_element_factory_make ("appsink", "audio-output");
- if (!pipeline || !source || !conv || !resampler || !encoder || !sink)
+ if (!pipeline || !filter || !source || !conv || !resampler || !encoder ||
!oggmux || !sink)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"One element could not be created. Exiting.\n");
@@ -185,6 +212,17 @@
/* Set up the pipeline */
+ caps = gst_caps_new_simple ("audio/x-raw",
+ "format", G_TYPE_STRING, "S16LE",
+/* "rate", G_TYPE_INT, SAMPLING_RATE,*/
+ "channels", G_TYPE_INT, OPUS_CHANNELS,
+/* "layout", G_TYPE_STRING, "interleaved",*/
+ NULL);
+ g_object_set (G_OBJECT (filter),
+ "caps", caps,
+ NULL);
+ gst_caps_unref (caps);
+
g_object_set (G_OBJECT (encoder),
/* "bitrate", 64000, */
/* "bandwidth", OPUS_BANDWIDTH_FULLBAND, */
@@ -194,7 +232,12 @@
"audio", FALSE, /* VoIP, not audio */
"frame-size", OPUS_FRAME_SIZE,
NULL);
-
+
+ g_object_set (G_OBJECT (oggmux),
+ "max-delay", OGG_MAX_DELAY,
+ "max-page-delay", OGG_MAX_PAGE_DELAY,
+ NULL);
+
/* we add a message handler */
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch (bus, bus_call, pipeline);
@@ -202,11 +245,11 @@
/* we add all elements into the pipeline */
/* audiosource | converter | resampler | opus-encoder | audio-output */
- gst_bin_add_many (GST_BIN (pipeline), source, conv, resampler, encoder,
- sink, NULL);
+ gst_bin_add_many (GST_BIN (pipeline), source, filter, conv, resampler,
encoder,
+ oggmux, sink, NULL);
/* we link the elements together */
- gst_element_link_many (source, conv, resampler, encoder, sink, NULL);
+ gst_element_link_many (source, filter, conv, resampler, encoder, oggmux,
sink, NULL);
/* Set the pipeline to "playing" state*/
GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n");
@@ -288,6 +331,10 @@
ssize_t ret;
if (0 == phase)
{
+#ifdef DEBUG_RECORD_PURE_OGG
+ if (dump_pure_ogg)
+ continue;
+#endif
ptr = (const char *) &audio_message;
to_send = sizeof (audio_message);
}
@@ -308,7 +355,7 @@
"Failed to write %u bytes at offset %u (total %u) in phase %d:
%s\n",
(unsigned int) to_send - offset, (unsigned int) offset,
(unsigned int) (to_send + offset), phase, strerror (errno));
- abort_send = 1;
+ abort_send = 1;
break;
}
}
Modified: gnunet/src/conversation/gnunet-helper-audio-record.c
===================================================================
--- gnunet/src/conversation/gnunet-helper-audio-record.c 2014-01-16
23:38:03 UTC (rev 31939)
+++ gnunet/src/conversation/gnunet-helper-audio-record.c 2014-01-17
04:12:51 UTC (rev 31940)
@@ -38,19 +38,143 @@
#include <pulse/pulseaudio.h>
#include <opus/opus.h>
#include <opus/opus_types.h>
+#include <ogg/ogg.h>
+#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 = SAMPLING_RATE,
- .channels = 1
+ .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
*/
@@ -82,7 +206,7 @@
static OpusEncoder *enc;
/**
- *
+ * Buffer for encoded data
*/
static unsigned char *opus_data;
@@ -97,16 +221,6 @@
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 char *transmit_buffer;
@@ -126,8 +240,30 @@
*/
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
*/
static void
@@ -138,20 +274,59 @@
}
+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 ()
{
- 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)
{
@@ -160,37 +335,42 @@
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);
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);
+ 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);
- 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)
+ while (ogg_stream_flush_fill (&os, &og, PAGE_WATERLINE))
{
- 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);
}
}
@@ -460,28 +640,113 @@
static void
opus_init ()
{
- int channels = 1;
int err;
- frame_size = SAMPLING_RATE / 50;
- pcm_length = frame_size * channels * sizeof (float);
+ pcm_length = FRAME_SIZE * CHANNELS * sizeof (float);
pcm_buffer = pa_xmalloc (pcm_length);
- opus_data = GNUNET_malloc (max_payload_bytes);
+ opus_data = GNUNET_malloc (MAX_PAYLOAD_BYTES);
enc = opus_encoder_create (SAMPLING_RATE,
- channels,
- OPUS_APPLICATION_VOIP,
+ CHANNELS,
+ CONV_OPUS_APP_TYPE,
&err);
opus_encoder_ctl (enc,
- OPUS_SET_PACKET_LOSS_PERC(1));
+ OPUS_SET_PACKET_LOSS_PERC
(CONV_OPUS_PACKET_LOSS_PERCENTAGE));
opus_encoder_ctl (enc,
- OPUS_SET_COMPLEXITY(10));
+ OPUS_SET_COMPLEXITY (CONV_OPUS_ENCODING_COMPLEXITY));
opus_encoder_ctl (enc,
- OPUS_SET_INBAND_FEC(1));
+ 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.
*
@@ -500,6 +765,11 @@
"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;
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [GNUnet-SVN] r31940 - gnunet/src/conversation,
gnunet <=