voxl-streamer does not handle "teardown-request" signal correctly



  • See https://forum.modalai.com/topic/1615/issue-with-voxl-streamer-not-reconnecting?_=1672332432641 for more context. It seems that the problem is that when a client initially disconnects, it returns a "teardown-request" not a "closed" signal, rtsp-client signal specifically, which never closes the client correctly. What happens then is that when the client tries to reconnect, it does not and disconnects automatically. Is there a better way of handling this, is there a way of closing the pipeline when a "teardown-request" is received. I have tried running the

    gst_rtsp_client_close
    

    and the

    static gboolean
    remove_sessions (GstRTSPServer * server)
    {
      GstRTSPSessionPool *pool;
      pool = gst_rtsp_server_get_session_pool (server);
      guint removed = gst_rtsp_session_pool_cleanup (pool);
      g_object_unref (pool);
      M_PRINT("Removed %d sessions\n", removed);
    
      return TRUE;
    }
    

    but it seems not to work and just gives me the following error of pthread, is there something I am not taking into consideration?
    Section of code that was modified from the original main.c file can be below:

    
    static gboolean
    remove_sessions (GstRTSPServer * server)
    {
      GstRTSPSessionPool *pool;
      pool = gst_rtsp_server_get_session_pool (server);
      guint removed = gst_rtsp_session_pool_cleanup (pool);
      g_object_unref (pool);
      M_PRINT("Removed %d sessions\n", removed);
    
      return TRUE;
    }
    
    static void rtsp_client_tear_down_request(GstRTSPClient* self, context_data *data) {
        M_PRINT("An existing client has requested tear down from the RTSP server\n");
    
        
        // remove_sessions(data->rtsp_server);
        gst_rtsp_client_close(self);
        pthread_mutex_lock(&data->lock);
        data->num_rtsp_clients--;
    
        if ( ! data->num_rtsp_clients) {
            data->input_frame_number = 0;
            data->output_frame_number = 0;
            data->need_data = 0;
            data->initial_timestamp = 0;
            data->last_timestamp = 0;
        }
        pthread_mutex_unlock(&data->lock);
    }
    
    // This is called by the RTSP server when a client has connected.
    static void rtsp_client_connected(GstRTSPServer* self, GstRTSPClient* object,
                                      context_data *data) {
        M_PRINT("A new client has connected to the RTSP server\n");
    
        pthread_mutex_lock(&data->lock);
        data->num_rtsp_clients++;
    
        // Install the disconnect callback with the client.
        g_signal_connect(object, "closed", G_CALLBACK(rtsp_client_disconnected), data);
       
    
        // Install tear down callback with the client
        g_signal_connect(object, "teardown-request", G_CALLBACK(rtsp_client_tear_down_request), data);
        pthread_mutex_unlock(&data->lock);
    }
    


  • It seems that the problem is the first ip port never gets closed which causes a conflict.

    ec 30 14:50:04 robot133 voxl-streamer[5967]: Stream available at rtsp://127.0.0.1:8900/live
    Dec 30 14:50:04 robot133 voxl-streamer[5967]: A new client has connected to the RTSP server
    Dec 30 14:50:04 robot133 voxl-streamer[5967]: [1]A new client rtsp://92.185.19.128:56804(null) has connected
    Dec 30 14:50:04 robot133 voxl-streamer[5967]: gbm_create_device(156): Info: backend name is: msm_drm
    Dec 30 14:50:04 robot133 voxl-streamer[5967]: gbm_create_device(156): Info: backend name is: msm_drm
    Dec 30 14:50:44 robot133 voxl-streamer[5967]: A new client has connected to the RTSP server
    Dec 30 14:50:44 robot133 voxl-streamer[5967]: [2]A new client rtsp://92.185.19.128:38300(null) has connected
    Dec 30 14:51:46 robot133 voxl-streamer[5967]: A new client has connected to the RTSP server
    
    

  • Dev Team

    For clarification, if you lose your stream via losing wifi connection or similar, the stream does not return when wifi returns?

    This is RB5 drone and what does voxl-version say for full software listing?



  • @RichieRich said in voxl-streamer does not handle "teardown-request" signal correctly:

    listing

    we were able to find a workaround.
    The voxl version is the following.

    --------------------------------------------------------------------------------
    system-image: 1.4.1-M0052-14.1a-perf
    kernel:       #1 SMP PREEMPT Thu Oct 13 18:59:44 UTC 2022 4.19.125
    --------------------------------------------------------------------------------
    hw version:   M0052
    --------------------------------------------------------------------------------
    voxl-suite:   0.9.4
    --------------------------------------------------------------------------------
    Packages:
    Repo:  http://voxl-packages.modalai.com/ ./dists/qrb5165/sdk-0.9/binary-arm64/
    Last Updated: 2022-12-27 18:53:03
    List:
    	libmodal-cv              0.2.3
    	libmodal-exposure        0.0.7
    	libmodal-journal         0.2.1
    	libmodal-json            0.4.3
    	libmodal-pipe            2.6.0
    	libqrb5165-io            0.1.0
    	libvoxl-cci-direct       0.1.5
    	libvoxl-cutils           0.1.1
    	mv-voxl                  0.1-r0
    	qrb5165-bind             0.1-r0
    	qrb5165-dfs-server       0.1.0
    	qrb5165-imu-server       0.5.0
    	qrb5165-slpi-test-sig    01-r0
    	qrb5165-system-tweaks    0.1.5
    	qrb5165-tflite           2.8.0-2
    	voxl-bind                0.0.1
    	voxl-camera-calibration  0.2.3
    	voxl-camera-server       1.3.5
    	voxl-cpu-monitor         0.3.0
    	voxl-docker-support      1.2.4
    	voxl-gphoto2-server      0.0.10
    	voxl-jpeg-turbo          2.1.3-4
    	voxl-libgphoto2          0.0.4
    	voxl-libuvc              1.0.7
    	voxl-logger              0.3.4
    	voxl-mavlink             0.1.0
    	voxl-mavlink-server      0.2.0
    	voxl-modem               0.16.1
    	voxl-mongoose            7.7.0-1
    	voxl-mpa-to-ros          0.3.6
    	voxl-mpa-tools           0.7.6
    	voxl-opencv              4.5.5-1
    	voxl-portal              0.4.2
    	voxl-px4                 1.12.31
    	voxl-px4-imu-server      0.1.2
    	voxl-qvio-server         0.8.2
    	voxl-remote-id           0.0.5
    	voxl-streamer            0.4.1
    	voxl-suite               0.9.4
    	voxl-tag-detector        0.0.4
    	voxl-tflite-server       0.3.1
    	voxl-utils               1.2.2
    	voxl-uvc-server          0.1.3
    	voxl-vision-px4          1.4.0
    
    

    We were able to solve the problem. @RichieRich . I will make a branch in voxl-streamer branch with the solution.

    Since I cant push the changes to the voxl-streamer gitlab repo, I will add the files changed.

    cmake_minimum_required(VERSION 3.3)
    project(voxl-streamer)
    
    add_definitions(-D_GNU_SOURCE)
    
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -std=c11 -Wall -pedantic")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_XOPEN_SOURCE=500 -fno-strict-aliasing")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -fno-zero-initialized-in-bss")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-function")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-cast-align")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-missing-braces -Wno-strict-aliasing")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")
    
    include_directories( SYSTEM /usr/include/gstreamer-1.0 )
    include_directories( SYSTEM /usr/include/glib-2.0 )
    
    include_directories( include/ )
    
    if(${BUILDSIZE} MATCHES 64)
        message("BUILDSIZE matches 64")
        include_directories( SYSTEM /usr/lib/aarch64-linux-gnu/glib-2.0/include )
        find_library(MODAL_JSON    modal_json    HINTS /usr/lib64)
        find_library(MODAL_PIPE    modal_pipe    HINTS /usr/lib64)
        find_library(MODAL_JOURNAL modal_journal HINTS /usr/lib64)
    else()
        message("BUILDSIZE does not match 64")
        include_directories( SYSTEM /usr/lib/glib-2.0/include )
        find_library(MODAL_JSON    modal_json    HINTS /usr/lib)
        find_library(MODAL_PIPE    modal_pipe    HINTS /usr/lib)
        find_library(MODAL_JOURNAL modal_journal HINTS /usr/lib)
    endif()
    
    add_executable( voxl-streamer
        src/util.c
        src/pipeline.c
        src/configuration.c
        src/main.c
    )
    
    target_link_libraries(voxl-streamer
        pthread
        gstreamer-1.0
        gstvideo-1.0
        gstrtspserver-1.0
        gobject-2.0
        gstapp-1.0
        glib-2.0
        gstrtsp-1.0
        ${MODAL_JSON}
        ${MODAL_PIPE}
        ${MODAL_JOURNAL}
    )
    
    add_executable( voxl-configure-streamer
        config/voxl-configure-streamer.c
    )
    
    target_link_libraries(voxl-configure-streamer
        ${MODAL_JSON}
    )
    
    install(
        TARGETS voxl-streamer voxl-configure-streamer
        DESTINATION /usr/bin
    )
    
    

    main.c

    /*******************************************************************************
     * Copyright 2020 ModalAI Inc.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice,
     *    this list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form must reproduce the above copyright notice,
     *    this list of conditions and the following disclaimer in the documentation
     *    and/or other materials provided with the distribution.
     *
     * 3. Neither the name of the copyright holder nor the names of its contributors
     *    may be used to endorse or promote products derived from this software
     *    without specific prior written permission.
     *
     * 4. The Software is used solely in conjunction with devices provided by
     *    ModalAI Inc.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     ******************************************************************************/
    #include <stdio.h>
    #include <stdint.h>
    #include <getopt.h>
    #include <string.h>
    #include <inttypes.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <gst/gst.h>
    #include <gst/video/video.h>
    #include <gst/app/gstappsrc.h>
    #include <glib-object.h>
    #include <modal_pipe.h>
    #include <modal_journal.h>
    #include "context.h"
    #include "pipeline.h"
    #include "configuration.h"
    #include "gst/rtsp/gstrtspconnection.h"
    
    // This is the main data structure for the application. It is passed / shared
    // with other modules as needed.
    static context_data context;
    
    // called whenever we connect or reconnect to the server
    static void _cam_connect_cb(__attribute__((unused)) int ch, __attribute__((unused)) void* context)
    {
        M_PRINT("Camera server Connected\n");
    }
    
    
    // called whenever we disconnect from the server
    static void _cam_disconnect_cb(__attribute__((unused)) int ch, __attribute__((unused)) void* context)
    {
        M_PRINT("Camera server Disconnected\n");
    }
    
    // camera helper callback whenever a frame arrives
    static void _cam_helper_cb(
        __attribute__((unused))int ch,
                               camera_image_metadata_t meta,
                               char* frame,
                               void* context)
    {
    
        static int dump_meta_data = 1;
        context_data *ctx = (context_data*) context;
        GstMapInfo info;
        GstFlowReturn status;
    
        if (dump_meta_data) {
            M_DEBUG("Meta data from incoming frame:\n");
            M_DEBUG("\tmagic_number 0x%X \n", meta.magic_number);
            M_DEBUG("\ttimestamp_ns: %" PRIu64 "\n", meta.timestamp_ns);
            M_DEBUG("\tframe_id: %d\n", meta.frame_id);
            M_DEBUG("\twidth: %d\n", meta.width);
            M_DEBUG("\theight: %d\n", meta.height);
            M_DEBUG("\tsize_bytes: %d\n", meta.size_bytes);
            M_DEBUG("\tstride: %d\n", meta.stride);
            M_DEBUG("\texposure_ns: %d\n", meta.exposure_ns);
            M_DEBUG("\tgain: %d\n", meta.gain);
            M_DEBUG("\tformat: %d\n", meta.format);
            dump_meta_data = 0;
        }
    
        // Initialize the input parameters based on the incoming meta data
        if ( ! ctx->input_parameters_initialized) {
            // We need to estimate our frame rate. So get a timestamp from the
            // very first frame and then on the second frame figure out the delta time
            // between the two frames. Estimate frame rate based on that delta.
            if (ctx->last_timestamp == 0) {
                ctx->last_timestamp = (guint64) meta.timestamp_ns;
                M_DEBUG("First frame timestamp: %" PRIu64 "\n", ctx->last_timestamp);
                return;
            } else {
                M_DEBUG("Second frame timestamp: %" PRIu64 "\n", (guint64) meta.timestamp_ns);
                guint64  delta_frame_time_ns = (guint64) meta.timestamp_ns - ctx->last_timestamp;
                M_DEBUG("Calculated frame delta in ns: %" PRIu64 "\n", delta_frame_time_ns);
                uint32_t delta_frame_time_100us = delta_frame_time_ns / 100000;
                M_DEBUG("Calculated frame delta in 100us: %u\n", delta_frame_time_100us);
                ctx->input_frame_rate = (uint32_t) ((10000.0 / (double) delta_frame_time_100us) + 0.5);
                M_DEBUG("Calculated input frame rate is: %u\n", ctx->input_frame_rate);
                ctx->input_frame_rate /= ctx->output_frame_decimator;
                ctx->output_frame_rate = ctx->input_frame_rate;
                M_DEBUG("Output frame rate will be: %u\n", ctx->input_frame_rate);
            }
            ctx->last_timestamp = (guint64) meta.timestamp_ns;
    
            if(ctx->output_stream_rotation == 90 || ctx->output_stream_rotation == 270){
                ctx->output_stream_height = meta.width;
                ctx->output_stream_width = meta.height;
            } else {
                ctx->output_stream_height = meta.height;
                ctx->output_stream_width = meta.width;
            }
    
            switch (meta.format) {
            case IMAGE_FORMAT_RAW8:
            case IMAGE_FORMAT_STEREO_RAW8:
                strncpy(ctx->input_frame_format, "gray8",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
    
            //For stereo color images we only send the first one, treat it as if only one came through
            case IMAGE_FORMAT_STEREO_NV12:
                meta.size_bytes /= 2;
            case IMAGE_FORMAT_NV12:
                strncpy(ctx->input_frame_format, "nv12",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
    
            //For stereo color images we only send the first one, treat it as if only one came through
            case IMAGE_FORMAT_STEREO_NV21:
                meta.size_bytes /= 2;
            case IMAGE_FORMAT_NV21:
                strncpy(ctx->input_frame_format, "nv21",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
            case IMAGE_FORMAT_YUV422:
                strncpy(ctx->input_frame_format, "yuyv",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
            case IMAGE_FORMAT_YUV422_UYVY:
                strncpy(ctx->input_frame_format, "uyvy",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
            case IMAGE_FORMAT_YUV420:
                strncpy(ctx->input_frame_format, "yuv420",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
            case IMAGE_FORMAT_RGB:
                strncpy(ctx->input_frame_format, "rgb",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
            case IMAGE_FORMAT_RAW16:
                strncpy(ctx->input_frame_format, "gray16",
                        MAX_IMAGE_FORMAT_STRING_LENGTH);
                break;
            default:
                M_ERROR("Unsupported frame format %d\n", meta.format);
                main_running = 0;
                break;
            }
            if ( ! main_running) return;
    
            ctx->input_frame_width = meta.width;
            if (meta.format == IMAGE_FORMAT_STEREO_RAW8) {
                // The 2 stereo images are stacked on top of each other
                // so have to double the height to account for both of them
                M_DEBUG("Got stereo raw8 as input format\n");
                ctx->input_frame_height = meta.height * 2;
            } else {
                ctx->input_frame_height = meta.height;
            }
    
            configure_frame_format(ctx->input_frame_format, ctx);
    
            if (ctx->input_frame_size != (uint32_t) meta.size_bytes) {
                M_ERROR("Frame size mismatch %d %d\n",
                        meta.size_bytes,
                        ctx->input_frame_size);
                main_running = 0;
                return;
            }
    
            ctx->input_parameters_initialized = 1;
        }
    
        // Allocate a gstreamer buffer to hold the frame data
        GstBuffer *gst_buffer = gst_buffer_new_and_alloc(ctx->input_frame_size);
        gst_buffer_map(gst_buffer, &info, GST_MAP_WRITE);
    
        if (info.size < ctx->input_frame_size) {
            M_ERROR("Not enough memory for the frame buffer\n");
            main_running = 0;
            return;
        }
    
        memcpy(info.data, frame, ctx->input_frame_size);
    
        // The need_data flag is set by the pipeline callback asking for
        // more data.
        if (ctx->need_data) {
            // If the input frame rate is higher than the output frame rate then
            // we ignore some of the frames.
            if ( ! (ctx->input_frame_number % ctx->output_frame_decimator)) {
    
                pthread_mutex_lock(&ctx->lock);
    
                // To get minimal latency make sure to set this to the timestamp of
                // the very first frame that we will be sending through the pipeline.
                if (ctx->initial_timestamp == 0) ctx->initial_timestamp = (guint64) meta.timestamp_ns;
    
                // Do the timestamp calculations.
                // It is very important to set these up accurately.
                // Otherwise, the stream can look bad or just not work at all.
                // TODO: Experiment with taking some time off of pts???
                GST_BUFFER_TIMESTAMP(gst_buffer) = ((guint64) meta.timestamp_ns - ctx->initial_timestamp);
                GST_BUFFER_DURATION(gst_buffer) = ((guint64) meta.timestamp_ns) - ctx->last_timestamp;
                ctx->last_timestamp = (guint64) meta.timestamp_ns;
    
                pthread_mutex_unlock(&ctx->lock);
    
                M_VERBOSE("Output frame %d %" PRIu64 " %" PRIu64 "\n",
                    ctx->output_frame_number,
                    GST_BUFFER_TIMESTAMP(gst_buffer),
                    GST_BUFFER_DURATION(gst_buffer));
    
                ctx->output_frame_number++;
    
                // Signal that the frame is ready for use
                g_signal_emit_by_name(ctx->app_source, "push-buffer", gst_buffer, &status);
                if (status == GST_FLOW_OK) {
                    M_VERBOSE("Frame %d accepted\n", ctx->output_frame_number);
                } else {
                    M_ERROR("New frame rejected\n");
                }
            }
    
            ctx->input_frame_number++;
    
        } else {
            M_VERBOSE("*** Skipping buffer ***\n");
        }
    
        // Release the buffer so that we don't have a memory leak
        gst_buffer_unmap(gst_buffer, &info);
        gst_buffer_unref(gst_buffer);
    
    }
    // This callback lets us know when an RTSP client has disconnected so that
    // we can stop trying to feed video frames to the pipeline and reset everything
    // for the next connection.
    static void rtsp_client_disconnected(GstRTSPClient* self, context_data *data) {
        M_PRINT("An existing client has disconnected from the RTSP server\n");
    
        pthread_mutex_lock(&data->lock);
        data->num_rtsp_clients--;
    
        if ( ! data->num_rtsp_clients) {
            data->input_frame_number = 0;
            data->output_frame_number = 0;
            data->need_data = 0;
            data->initial_timestamp = 0;
            data->last_timestamp = 0;
        }
        pthread_mutex_unlock(&data->lock);
    }
    
    // Gts client information 
    static void print_client_info(GstRTSPClient* object)
    {
        GstRTSPConnection *connection = gst_rtsp_client_get_connection(object);
        if (connection == NULL)
        {
            M_PRINT("Could not get RTSP connection\n");
            // DEBUG_EXIT;
            return;
        }
    
        GstRTSPUrl *url = gst_rtsp_connection_get_url(connection);
        if (url == NULL)
        {
            M_PRINT("Could not get RTSP connection URL\n");
            // DEBUG_EXIT;
            return;
        }
        gchar* uri = gst_rtsp_url_get_request_uri(url);
        M_PRINT("A new client %s has connected\n",uri);
        g_free(uri);
    }
    
    // This is called by the RTSP server when a client has connected.
    static void rtsp_client_connected(GstRTSPServer* self, GstRTSPClient* object,
                                      context_data *data) {
        M_PRINT("A new client has connected to the RTSP server\n");
    
        pthread_mutex_lock(&data->lock);
        print_client_info(object);
        data->num_rtsp_clients++;
    
        // Install the disconnect callback with the client.
        g_signal_connect(object, "closed", G_CALLBACK(rtsp_client_disconnected), data);
        pthread_mutex_unlock(&data->lock);
    }
    /* this timeout is periodically run to clean up the expired sessions from the
     * pool. This needs to be run explicitly currently but might be done
     * automatically as part of the mainloop. */
    static gboolean
    timeout (gpointer data)
    {
      context_data *context_data_local = (context_data *)data;
      GstRTSPSessionPool *pool;
      pool = gst_rtsp_server_get_session_pool (context_data_local->rtsp_server);
      guint removed = gst_rtsp_session_pool_cleanup (pool);
      g_object_unref (pool);
    //   M_PRINT("Removed sessions: %d\n", removed);
      if (removed > 0)
      {
        M_PRINT("Removed %d sessions\n", removed);
        pthread_mutex_lock(&context_data_local->lock);
        context_data_local->num_rtsp_clients--;
    
        if ( ! context_data_local->num_rtsp_clients) {
            context_data_local->input_frame_number = 0;
            context_data_local->output_frame_number = 0;
            context_data_local->need_data = 0;
            context_data_local->initial_timestamp = 0;
            context_data_local->last_timestamp = 0;
        }
    
        pthread_mutex_unlock(&context_data_local->lock);
      }
    
      return TRUE;
    }
    // This callback is setup to happen at 1 second intervals so that we can
    // monitor when the program is ending and exit the main loop.
    gboolean loop_callback(gpointer data) {
        if ( ! main_running) {
            M_PRINT("Trying to stop loop\n");
            g_main_loop_quit((GMainLoop*) data);
        }
        return TRUE;
    }
    
    // This will cause all remaining RTSP clients to be removed
    GstRTSPFilterResult stop_rtsp_clients(GstRTSPServer* server,
                                          GstRTSPClient* client,
                                          gpointer user_data) {
        return  GST_RTSP_FILTER_REMOVE;
    }
    
    static void PrintHelpMessage()
    {
        M_PRINT("\nCommand line arguments are as follows:\n\n");
        M_PRINT("-b --bitrate    <#>     | Use specified bitrate instead of one in config file\n");
        M_PRINT("-d --decimator  <#>     | Use specified decimator instead of one in config file\n");
        M_PRINT("-h --help               | Print this help message\n");
        M_PRINT("-i --input-pipe <name>  | Use specified input pipe instead of one in config file\n");
        M_PRINT("-p --port       <#>     | Use specified port number for the RTSP URI instead of config file\n");
        M_PRINT("-r --rotation   <#>     | Use specified rotation instead of one in config file\n");
        M_PRINT("-s --software-encode    | Use software h264 encoder instead of OMX hardware encoder.\n");
        M_PRINT("-v --verbosity  <#>     | Log verbosity level (Default 2)\n");
        M_PRINT("                      0 | Print verbose logs\n");
        M_PRINT("                      1 | Print >= info logs\n");
        M_PRINT("                      2 | Print >= warning logs\n");
        M_PRINT("                      3 | Print only fatal logs\n");
    }
    
    static int ParseArgs(int         argc,                 ///< Number of arguments
                         char* const argv[])               ///< Argument list
    {
        static struct option LongOptions[] =
        {
            {"bitrate",          required_argument,  0, 'b'},
            {"decimator",        required_argument,  0, 'd'},
            {"help",             no_argument,        0, 'h'},
            {"input-pipe",       required_argument,  0, 'i'},
            {"port",             required_argument,  0, 'p'},
            {"rotation",         required_argument,  0, 'r'},
            {"software-encode",  no_argument,        0, 's'},
            {"verbosity",        required_argument,  0, 'v'},
        };
    
        int optionIndex = 0;
        int option;
    
        while ((option = getopt_long (argc, argv, ":b:d:hi:p:r:sv:", &LongOptions[0], &optionIndex)) != -1)
        {
            switch (option) {
                case 'v':{
                    int verbosity;
                    if (sscanf(optarg, "%d", &verbosity) != 1)
                    {
                        M_ERROR("Failed to parse debug level specified after -d flag\n");
                        return -1;
                    }
    
                    if (verbosity >= PRINT || verbosity < VERBOSE)
                    {
                        M_ERROR("Invalid debug level specified: %d\n", verbosity);
                        return -1;
                    }
    
                    M_JournalSetLevel((M_JournalLevel) verbosity);
                    break;
                }
                case 'b':
                    if(sscanf(optarg, "%u", &context.output_stream_bitrate) != 1){
                        M_ERROR("Failed to get valid integer for bitrate from: %s\n", optarg);
                        return -1;
                    }
                    break;
                case 'd':
                    if(sscanf(optarg, "%u", &context.output_frame_decimator) != 1){
                        M_ERROR("Failed to get valid integer for decimator from: %s\n", optarg);
                        return -1;
                    }
                    break;
                case 'r':
                    if(sscanf(optarg, "%u", &context.output_stream_rotation) != 1){
                        M_ERROR("Failed to get valid integer for rotation from: %s\n", optarg);
                        return -1;
                    }
                    if( context.output_stream_rotation != 0   &&
                        context.output_stream_rotation != 90  &&
                        context.output_stream_rotation != 180 &&
                        context.output_stream_rotation != 270 ) {
                        M_ERROR("Invalid rotation: %u, must be 0, 90, 180, or 270\n", context.output_stream_rotation);
                        return -1;
                    }
                    break;
                case 'i':
                    strncpy(context.input_pipe_name, optarg, MODAL_PIPE_MAX_PATH_LEN);
                    break;
                case 'p':
                    strncpy(context.rtsp_server_port, optarg, MAX_RTSP_PORT_SIZE);
                    break;
                case 's':
                    M_PRINT("Enabling software h264 encoder instead of OMX\n");
                    context.use_sw_h264 = 1;
                    break;
                case 'h':
                    PrintHelpMessage();
                    return -1;
                case ':':
                    M_ERROR("Option %c needs a value\n\n", optopt);
                    PrintHelpMessage();
                    return -1;
                case '?':
                    M_ERROR("Unknown option: %c\n\n", optopt);
                    PrintHelpMessage();
                    return -1;
            }
        }
    
        // optind is for the extra arguments which are not parsed
        if(optind < argc){
            M_WARN("extra arguments:\n");
            for (; optind < argc; optind++) {
                M_WARN("\t%s\n", argv[optind]);
            }
        }
    
        return 0;
    }
    
    //--------
    //  Main
    //--------
    int main(int argc, char *argv[]) {
        GstRTSPMountPoints *mounts;
        GstRTSPMediaFactory *factory;
        GMainContext* loop_context;
        GMainLoop *loop;
        GSource *loop_source;
        GSource *time_loop_source;
        strncpy(context.rtsp_server_port, DEFAULT_RTSP_PORT, MAX_RTSP_PORT_SIZE);
    
        // Have the configuration module fill in the context data structure
        // with all of the required parameters to support the given configuration
        // in the given configuration file.
        if (prepare_configuration(&context)) {
            M_ERROR("Could not parse the configuration data\n");
            return -1;
        }
    
        if(ParseArgs(argc, argv)){
            M_ERROR("Failed to parse args\n");
            return -1;
        }
    
        // start signal handler so we can exit cleanly
        main_running = 1;
        if(enable_signal_handler()==-1){
            M_ERROR("Failed to start signal handler\n");
            return -1;
        }
    
        M_DEBUG("Using input:     %s\n", context.input_pipe_name);
        M_DEBUG("Using RTSP port: %s\n", context.rtsp_server_port);
        M_DEBUG("Using bitrate:   %d\n", context.output_stream_bitrate);
        M_DEBUG("Using roatation: %d\n", context.output_stream_rotation);
        M_DEBUG("Using decimator: %d\n", context.output_frame_decimator);
    
    
        // Pass a pointer to the context to the pipeline module
        pipeline_init(&context);
    
        // Initialize Gstreamer
        gst_init(NULL, NULL);
    
    
        pipe_client_set_connect_cb(0, _cam_connect_cb, NULL);
        pipe_client_set_disconnect_cb(0, _cam_disconnect_cb, NULL);
        pipe_client_set_camera_helper_cb(0, _cam_helper_cb, &context);
        pipe_client_open(0, context.input_pipe_name, "voxl-streamer", EN_PIPE_CLIENT_CAMERA_HELPER, 0);
    
    
        while (main_running) {
            if (context.input_parameters_initialized) break;
            usleep(500000);
        }
        if (context.input_parameters_initialized) {
            M_DEBUG("Input parameters initialized\n");
        } else {
            M_ERROR("Timeout on input parameter initialization\n");
            return -1;
        }
    
        // Create the RTSP server
        context.rtsp_server = gst_rtsp_server_new();
        if (context.rtsp_server) {
            M_DEBUG("Made rtsp_server\n");
        } else {
            M_ERROR("couldn't make rtsp_server\n");
            return -1;
        }
    
        // Configure the RTSP server port
        g_object_set(context.rtsp_server, "service", context.rtsp_server_port, NULL);
    
        // Setup the callback to alert when a new connection has come in
        g_signal_connect(context.rtsp_server, "client-connected",
                         G_CALLBACK(rtsp_client_connected), &context);
    
        // Setup the glib loop for the RTSP server to use
        loop_context = g_main_context_new();
        if (loop_context) {
            M_DEBUG("Created RTSP server main loop context\n");
        } else {
            M_ERROR("Couldn't create loop context\n");
            return -1;
        }
        loop = g_main_loop_new(loop_context, FALSE);
        if (loop) {
            M_DEBUG("Created RTSP server main loop\n");
        } else {
            M_ERROR("Couldn't create loop\n");
            return -1;
        }
    
        // Add an event source to the loop context. This will generate a callback
        // at 1 second intervals. We can use this to detect when the program is
        // ending and exit the loop
        loop_source = g_timeout_source_new_seconds(1);
        if (loop_source) {
            M_DEBUG("Created RTSP server main loop source for callback\n");
        } else {
            M_ERROR("Couldn't create loop source for callback\n");
            return -1;
        }
        g_source_set_callback(loop_source, loop_callback, loop, NULL);
        g_source_attach(loop_source, loop_context);
    
          // attach timeout to out loop 
        time_loop_source = g_timeout_source_new_seconds(2);
        if (time_loop_source) {
            M_DEBUG("Created timeout loop source for callback\n");
        } else {
            M_ERROR("Couldn't create timeout loop source for callback\n");
            return -1;
        }
        g_source_set_callback(time_loop_source, timeout, &context, NULL);
        g_source_attach(time_loop_source, loop_context);
    
        g_main_context_unref(loop_context);
        g_source_unref(loop_source);
        g_source_unref(time_loop_source);
    
        // get the mount points for the RTSP server, every server has a default object
        // that will be used to map uri mount points to media factories
        mounts = gst_rtsp_server_get_mount_points(context.rtsp_server);
        if ( ! mounts) {
            M_ERROR("Couldn't get mount points\n");
            return -1;
        }
    
        // We override the create element function with our own so that we can use
        // our custom pipeline instead of a launch line.
        factory = gst_rtsp_media_factory_new();
        if ( ! factory) {
            M_ERROR("Couldn't create new media factory\n");
            return -1;
        }
        // Set as shared to consider video disconnects 
        gst_rtsp_media_factory_set_shared (factory, TRUE);
        GstRTSPMediaFactoryClass *memberFunctions = GST_RTSP_MEDIA_FACTORY_GET_CLASS(factory);
        if ( ! memberFunctions) {
            M_ERROR("Couldn't get media factory class pointer\n");
            return -1;
        }
        memberFunctions->create_element = create_custom_element;
        char *link_name = "/live";
        gst_rtsp_mount_points_add_factory(mounts, link_name, factory);
        g_object_unref(mounts);
    
        // Attach the RTSP server to our loop
        int source_id = gst_rtsp_server_attach(context.rtsp_server, loop_context);
        M_DEBUG("Got %d from gst_rtsp_server_attach\n", source_id);
        if ( ! source_id) {
            M_ERROR("gst_rtsp_server_attach failed\n");
            return -1;
        }
    
      
    
        // Indicate how to connect to the stream
        M_PRINT("Stream available at rtsp://127.0.0.1:%s%s\n", context.rtsp_server_port,
               link_name);
    
        // Start the main loop that the RTSP Server is attached to. This will not
        // exit until it is stopped.
        g_main_loop_run(loop);
        g_main_loop_unref(loop);
    
        // Main loop has exited, time to clean up and exit the program
        M_DEBUG("g_main_loop exited\n");
    
        // Wait for the buffer processing thread to exit
        pipe_client_close_all();
    
        // Stop any remaining RTSP clients
        (void) gst_rtsp_server_client_filter(context.rtsp_server, stop_rtsp_clients, NULL);
    
        // Clean up gstreamer
        gst_deinit();
    
        M_PRINT("Exited Cleanly\n");
    
        return 0;
    }
    
    

  • Dev Team

    this is very helpful, thank you. We will try to incorporate. We do accept pull requests on our gitlab for future reference



  • @Chad-Sweet @RichieRich Just created a pull request, I though I couldn't. It is here https://gitlab.com/voxl-public/voxl-sdk/services/voxl-streamer/-/merge_requests/24. Would love to follow up on this, in case any changes are made from your side to incorporate in on our side.



  • Everything looks good, gave it a glance and a quick test, it's been merged to our dev and staging branches in v0.4.2 and will be in the next release, thanks!


Log in to reply