#include #include #include #include #include #include #include #include #include struct dcall_elements { GstElement *pipeline; GstElement *rx_tap, *rx_jitterbuffer, *rx_depay, *rx_opusdec, *rx_resample, *rx_echocancel, *rx_pulse, *rx_fakesink; GstElement *tx_pulse, *tx_filesrc, *tx_mpegaudioparse, *tx_mpgaudiodec, *tx_audioconvert, *tx_echocancel, *tx_queue, *tx_resample, *tx_opusenc, *tx_pay, *tx_sink; char *remote_host, *audio_tap, *audio_sink, *audio_file, *gstreamer_log_path; int remote_port, latency; guint64 grtppktlost; }; int create_rx_chain(struct dcall_elements* de) { de->rx_tap = gst_element_factory_make("udpsrc", "rx-tap"); de->rx_jitterbuffer = gst_element_factory_make("rtpjitterbuffer", "rx-jitterbuffer"); de->rx_depay = gst_element_factory_make("rtpopusdepay", "rx-depay"); de->rx_opusdec = gst_element_factory_make("opusdec", "rx-opusdec"); de->rx_resample = gst_element_factory_make("audioresample", "rx-audioresample"); de->rx_echocancel = gst_element_factory_make("webrtcechoprobe", "rx-echocancel"); de->rx_pulse = gst_element_factory_make("pulsesink", "rx-pulse"); de->rx_fakesink = gst_element_factory_make("fakesink", "rx-fakesink"); if (!de->rx_tap || !de->rx_jitterbuffer || !de->rx_depay || !de->rx_opusdec || !de->rx_resample || !de->rx_echocancel || !de->rx_pulse || !de->rx_fakesink) { g_printerr ("One element of the rx chain could not be created. Exiting.\n"); return -1; } g_object_set(G_OBJECT (de->rx_tap), "port", 5000, NULL); //g_object_set(G_OBJECT (rx_tap), "address", "127.0.0.1", NULL); g_object_set(G_OBJECT (de->rx_tap), "caps", gst_caps_new_simple("application/x-rtp", "media", G_TYPE_STRING, "audio", NULL), NULL); g_object_set(G_OBJECT (de->rx_jitterbuffer), "do-lost", TRUE, NULL); g_object_set(G_OBJECT (de->rx_jitterbuffer), "do-retransmission", FALSE, NULL); g_object_set(G_OBJECT (de->rx_jitterbuffer), "latency", de->latency, NULL); g_object_set(G_OBJECT (de->rx_jitterbuffer), "drop-on-latency", FALSE, NULL); //g_object_set(G_OBJECT (de->rx_jitterbuffer), "post-drop-messages", TRUE, NULL); g_object_set(G_OBJECT (de->rx_opusdec), "plc", TRUE, NULL); g_object_set(G_OBJECT (de->rx_opusdec), "use-inband-fec", FALSE, NULL); GstStructure *props; props = gst_structure_from_string ("props,media.role=phone", NULL); g_object_set (de->rx_pulse, "stream-properties", props, NULL); gst_structure_free (props); if (strcmp(de->audio_sink, "pulsesink") == 0) { gst_bin_add_many (GST_BIN (de->pipeline), de->rx_tap, de->rx_jitterbuffer, de->rx_depay, de->rx_opusdec, de->rx_resample, de->rx_echocancel, de->rx_pulse, NULL); gst_element_link_many (de->rx_tap, de->rx_jitterbuffer, de->rx_depay, de->rx_opusdec, de->rx_resample, de->rx_echocancel, de->rx_pulse, NULL); } else if (strcmp(de->audio_sink, "fakesink") == 0) { gst_bin_add_many (GST_BIN (de->pipeline), de->rx_tap, de->rx_jitterbuffer, de->rx_depay, de->rx_opusdec, de->rx_resample, de->rx_echocancel, de->rx_fakesink, NULL); gst_element_link_many (de->rx_tap, de->rx_jitterbuffer, de->rx_depay, de->rx_opusdec, de->rx_resample, de->rx_echocancel, de->rx_fakesink, NULL); } else { fprintf(stderr, "Wrong audio sink %s, exiting...\n", de->audio_sink); exit(EXIT_FAILURE); } return 0; } int create_tx_chain(struct dcall_elements* de) { de->tx_pulse = gst_element_factory_make("pulsesrc", "tx-pulse"); de->tx_filesrc = gst_element_factory_make("filesrc", "tx-filesrc"); de->tx_mpegaudioparse = gst_element_factory_make("mpegaudioparse", "tx-mpegaudioparse"); de->tx_mpgaudiodec = gst_element_factory_make("mpg123audiodec", "tx-mpgaudiodec"); de->tx_audioconvert = gst_element_factory_make("audioconvert", "tx-audioconvert"); de->tx_resample = gst_element_factory_make("audioresample", "tx-resample"); de->tx_echocancel = gst_element_factory_make("webrtcdsp", "tx-echocancel"); de->tx_queue = gst_element_factory_make("queue", "tx-queue"); de->tx_opusenc = gst_element_factory_make("opusenc", "tx-opusenc"); de->tx_pay = gst_element_factory_make("rtpopuspay", "tx-rtpopuspay"); de->tx_sink = gst_element_factory_make("udpsink", "tx-sink"); if (!de->tx_pulse || !de->tx_filesrc || !de->tx_mpegaudioparse || !de->tx_mpgaudiodec || !de->tx_audioconvert || !de->tx_echocancel || !de->tx_queue || !de->tx_resample || !de->tx_opusenc || !de->tx_pay || !de->tx_sink) { g_printerr("One element of the tx chain could not be created. Exiting.\n"); return -1; } gst_util_set_object_arg(G_OBJECT(de->tx_opusenc), "audio-type", "voice"); g_object_set(G_OBJECT(de->tx_opusenc), "inband-fec", FALSE, NULL); g_object_set(G_OBJECT(de->tx_opusenc), "frame-size", 40, NULL); g_object_set(G_OBJECT(de->tx_opusenc), "bitrate", 32000, NULL); g_object_set(G_OBJECT(de->tx_opusenc), "dtx", FALSE, NULL); // gstreamer dtx opus implem. is broken g_object_set(G_OBJECT(de->tx_sink), "host", de->remote_host, NULL); g_object_set(G_OBJECT(de->tx_sink), "port", de->remote_port, NULL); g_object_set(G_OBJECT(de->tx_sink), "async", FALSE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "echo-cancel", TRUE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "extended-filter", TRUE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "gain-control", TRUE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "high-pass-filter", TRUE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "limiter", FALSE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "noise-suppression", TRUE, NULL); g_object_set(G_OBJECT(de->tx_echocancel), "probe", "rx-echocancel", NULL); g_object_set(G_OBJECT(de->tx_echocancel), "voice-detection", FALSE, NULL); GstStructure *props; props = gst_structure_from_string ("props,media.role=phone", NULL); g_object_set (de->tx_pulse, "stream-properties", props, NULL); gst_structure_free (props); g_object_set(de->tx_filesrc, "location", de->audio_file, NULL); if (strcmp(de->audio_tap, "pulsesrc") == 0) { gst_bin_add_many(GST_BIN(de->pipeline), de->tx_pulse, de->tx_echocancel, de->tx_queue, de->tx_resample, de->tx_opusenc, de->tx_pay, de->tx_sink, NULL); gst_element_link_many(de->tx_pulse, de->tx_resample, de->tx_echocancel, de->tx_queue, de->tx_opusenc, de->tx_pay, de->tx_sink, NULL); } else if (strcmp(de->audio_tap, "filesrc") == 0) { gst_bin_add_many(GST_BIN(de->pipeline), de->tx_filesrc, de->tx_mpegaudioparse, de->tx_mpgaudiodec, de->tx_audioconvert, de->tx_queue, de->tx_resample, de->tx_opusenc, de->tx_pay, de->tx_sink, NULL); gst_element_link_many(de->tx_filesrc, de->tx_mpegaudioparse, de->tx_mpgaudiodec, de->tx_audioconvert, de->tx_resample, de->tx_queue, de->tx_opusenc, de->tx_pay, de->tx_sink, NULL); } else { fprintf(stderr, "Wrong audio tap %s, exiting...\n", de->audio_tap); exit(EXIT_FAILURE); } return 0; } static GstPadProbeReturn jitter_buffer_sink_event(GstPad *pad, GstPadProbeInfo *info, gpointer user_data) { struct dcall_elements *de = user_data; GstEvent *event = NULL; //g_print("Entering rtpjitterbuffer sink pad handler for events...\n"); event = gst_pad_probe_info_get_event (info); if (event == NULL) return GST_PAD_PROBE_OK; //g_print("We successfully extracted an event from the pad... \n"); const GstStructure *struc = NULL; struc = gst_event_get_structure(event); if (struc == NULL) return GST_PAD_PROBE_OK; //g_print("We successfully extracted a structure from the event... \n"); const gchar* struc_name = NULL; struc_name = gst_structure_get_name(struc); if (struc_name == NULL) return GST_PAD_PROBE_OK; //g_print("We extracted the structure \"%s\"...\n", struc_name); if (strcmp(struc_name, "GstRTPPacketLost") != 0) return GST_PAD_PROBE_OK; //g_print("And that's the structure we want !\n"); guint seqnum = 0, retry = 0; guint64 timestamp = 0, duration = 0; gst_structure_get_uint(struc, "seqnum", &seqnum); gst_structure_get_uint(struc, "retry", &retry); gst_structure_get_uint64(struc, "timestamp", ×tamp); gst_structure_get_uint64(struc, "duration", &duration); g_print("GstRTPPacketLost{seqnum=%d, retry=%d, duration=%ld, timestamp=%ld}\n", seqnum, retry, duration, timestamp); de->grtppktlost++; return GST_PAD_PROBE_OK; } gboolean stop_handler(gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit(loop); return TRUE; } int main(int argc, char *argv[]) { GMainLoop *loop; struct dcall_elements de = { .audio_file = "voice.mp3", .gstreamer_log_path = "dcall.log", .latency = 150, .remote_port = 5000, .remote_host = "127.13.3.7", .audio_sink = "pulsesink", .audio_tap = "pulsesrc", .grtppktlost = 0 }; int opt = 0; while ((opt = getopt(argc, argv, "t:s:r:p:l:d:a:h")) != -1) { switch(opt) { case 'a': //latency de.audio_file = optarg; break; case 'd': de.gstreamer_log_path = optarg; break; case 'l': de.latency = atoi(optarg); break; case 'p': de.remote_port = atoi(optarg); break; case 'r': de.remote_host = optarg; break; case 's': de.audio_sink = optarg; break; case 't': de.audio_tap = optarg; break; case 'h': default: g_print("Usage: %s [-a