/* This file is part of GNUnet. Copyright (C) 2013 GNUnet e.V. GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file conversation/gnunet-helper-audio-record-gst.c * @brief program to record audio data from the microphone (GStreamer version) * @author LRN */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_protocols.h" #include "conversation.h" #include "gnunet_constants.h" #include "gnunet_core_service.h" #include #include #include #include #define DEBUG_RECORD_PURE_OGG 1 /** * Number of channels. * Must be one of the following (from libopusenc documentation): * 1, 2 */ #define OPUS_CHANNELS 1 /** * Maximal size of a single opus packet. */ #define MAX_PAYLOAD_SIZE (1024 / OPUS_CHANNELS) /** * Size of a single frame fed to the encoder, in ms. * Must be one of the following (from libopus documentation): * 2.5, 5, 10, 20, 40 or 60 */ #define OPUS_FRAME_SIZE 40 /** * Expected packet loss to prepare for, in percents. */ #define PACKET_LOSS_PERCENTAGE 1 /** * Set to 1 to enable forward error correction. * Set to 0 to disable. */ #define INBAND_FEC_MODE 1 /** * Max number of microseconds to buffer in audiosource. * Default is 200000 */ #define BUFFER_TIME 1000 /* 1ms */ /** * Min number of microseconds to buffer in audiosource. * Default is 10000 */ #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 () { if (NULL != pipeline) gst_element_set_state (pipeline, GST_STATE_NULL); } static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Bus message\n"); switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_EOS: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "End of stream\n"); quit (); break; case GST_MESSAGE_ERROR: { 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; } default: break; } return TRUE; } void source_child_added (GstChildProxy *child_proxy, GObject *object, gchar *name, gpointer user_data) { if (GST_IS_AUDIO_BASE_SRC (object)) g_object_set (object, "buffer-time", (gint64) BUFFER_TIME, "latency-time", (gint64) LATENCY_TIME, NULL); } static void signalhandler (int s) { quit (); } int main (int argc, char **argv) { 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 /* Initialisation */ gst_init (&argc, &argv); GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("gnunet-helper-audio-record", "WARNING", NULL)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Audio source starts\n"); audio_message.header.type = htons (GNUNET_MESSAGE_TYPE_CONVERSATION_AUDIO); /* 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 || !filter || !source || !conv || !resampler || !encoder || !oggmux || !sink) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "One element could not be created. Exiting.\n"); return -1; } g_signal_connect (source, "child-added", G_CALLBACK (source_child_added), NULL); /* 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, */ "inband-fec", INBAND_FEC_MODE, "packet-loss-percentage", PACKET_LOSS_PERCENTAGE, "max-payload-size", MAX_PAYLOAD_SIZE, "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); gst_object_unref (bus); /* we add all elements into the pipeline */ /* audiosource | converter | resampler | opus-encoder | audio-output */ 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, filter, conv, resampler, encoder, oggmux, sink, NULL); /* Set the pipeline to "playing" state*/ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now playing\n"); gst_element_set_state (pipeline, GST_STATE_PLAYING); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running...\n"); /* Iterate */ while (!abort_send) { GstSample *s; GstBuffer *b; GstMapInfo m; size_t len, msg_size; const char *ptr; int phase; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulling...\n"); s = gst_app_sink_pull_sample (GST_APP_SINK (sink)); if (NULL == s) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pulled NULL\n"); break; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "...pulled!\n"); { const GstStructure *si; char *si_str; GstCaps *s_caps; char *caps_str; si = gst_sample_get_info (s); if (si) { si_str = gst_structure_to_string (si); if (si_str) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample %s\n", si_str); g_free (si_str); } } else GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no info\n"); s_caps = gst_sample_get_caps (s); if (s_caps) { caps_str = gst_caps_to_string (s_caps); if (caps_str) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with caps %s\n", caps_str); g_free (caps_str); } } else GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got sample with no caps\n"); } b = gst_sample_get_buffer (s); if (NULL == b || !gst_buffer_map (b, &m, GST_MAP_READ)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "got NULL buffer %p or failed to map the buffer\n", b); gst_sample_unref (s); continue; } len = m.size; if (len > UINT16_MAX - sizeof (struct AudioMessage)) { GNUNET_break (0); len = UINT16_MAX - sizeof (struct AudioMessage); } msg_size = sizeof (struct AudioMessage) + len; audio_message.header.size = htons ((uint16_t) msg_size); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending %u bytes of audio data\n", (unsigned int) msg_size); for (phase = 0; phase < 2; phase++) { size_t offset; size_t to_send; 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); } else { ptr = (const char *) m.data; to_send = len; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending %u bytes on phase %d\n", (unsigned int) to_send, phase); for (offset = 0; offset < to_send; offset += ret) { ret = write (1, &ptr[offset], to_send - offset); if (0 >= ret) { if (-1 == ret) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "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; break; } } if (abort_send) break; } gst_buffer_unmap (b, &m); gst_sample_unref (s); } signal (SIGINT, inthandler); signal (SIGINT, termhandler); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returned, stopping playback\n"); quit (); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting pipeline\n"); gst_object_unref (GST_OBJECT (pipeline)); pipeline = NULL; g_source_remove (bus_watch_id); return 0; }