Skip to content
This repository has been archived by the owner on Apr 14, 2020. It is now read-only.

Commit

Permalink
v1.2.3: Input loop (#33)
Browse files Browse the repository at this point in the history
* File input will automatically loop without EOS

* Pass references to fix codacy issues
  • Loading branch information
Ullaakut authored and Brendan LE GLAUNEC committed Oct 4, 2017
1 parent 49c8564 commit 72325a7
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 51 deletions.
19 changes: 4 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ function(find_sources DIRS)
set (HEADERS ${_headers} PARENT_SCOPE)
endfunction()


# Cmake properties
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON)
Expand Down Expand Up @@ -68,34 +67,24 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-covera
# find gstreamer 1.x libraries
pkg_search_module(GSTREAMER REQUIRED gstreamer-1.0)
find_library(LIB_GSTREAMER NAMES ${GSTREAMER_LIBRARIES} HINTS ${GSTREAMER_LIBRARY_DIRS})

FIND_LIBRARY(GST_RTSP_SERVER_LIBRARY_DIRS NAMES gstrtspserver-1.0 PATHS "/usr/lib/x86_64-linux-gnu/gstreamer-1.0/")

FIND_PATH(GST_RTSP_SERVER_INCLUDE_DIRS gst/rtsp-server/rtsp-server.h PATHS "/usr/include/gstreamer-1.0")
pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0) # for appsrc

include_directories (
"${PROJECT_SOURCE_DIR}/include"
"${GSTREAMER_INCLUDE_DIRS}"
"${GST_RTSP_SERVER_INCLUDE_DIRS}"
"${GSTREAMER_APP_INCLUDE_DIRS}"
)


link_directories (
"${GSTREAMER_LIBRARY_DIRS}"
"${GST_RTSP_SERVER_LIBRARY_DIRS}"
"${GSTREAMER_APP_LIBRARY_DIRS}"
)

if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
# search special osx gstreamer libs
pkg_search_module(GSTREAMER_APP REQUIRED gstreamer-app-1.0)
find_library(LIB_GSTREAMER NAMES ${GSTREAMER_APP_LIBRARIES} HINTS ${GSTREAMER_APP_LIBRARY_DIRS})

include_directories (${GSTREAMER_APP_INCLUDE_DIRS})

link_directories (${GSTREAMER_APP_LIBRARY_DIRS})
endif()

find_sources("src" "include")
add_executable (rtspatt ${HEADERS} ${SOURCES})

target_link_libraries (rtspatt ${GSTREAMER_LIBRARIES} ${GST_RTSP_SERVER_LIBRARY_DIRS})
target_link_libraries (rtspatt ${GSTREAMER_LIBRARIES} ${GST_RTSP_SERVER_LIBRARY_DIRS} ${GSTREAMER_APP_LIBRARIES})
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ All of these environment variables and command line arguments override the defau
Input used as video source. [default: `pattern:smtpe`]
- If the argument starts with `rtsp://` it will try to open it as an **RTSP stream**
- If it starts with `pattern:` if will create a **test video with the given pattern** (_see [this link](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-videotestsrc.html#GstVideoTestSrcPattern) for more information on this argument_)
- Otherwise it will use the argument as a **file input**.
- Otherwise it will use the argument as a absolute link to **file input**. Remember to use the `-v` option to insert a video into your container if you want to read it. Example: `docker run -e INPUT=/tmp/video.mp4 -v /home/test/documents/myVideo.mp4:/tmp/video -p 8554:8554 ullaakut/rtspatt`. The repository contains a 30 second sample video in the `samples` folder.
* `ENABLE_TIME_OVERLAY` | `-t`:
If the environment variable is set to true or the command line flag is used, a time overlay will be added to the stream. This can be useful to debug latency. [default: disabled] - RTSPATT will have to do encoding to add the time (CPU usage)
* `GST_DEBUG`:
Expand Down
13 changes: 10 additions & 3 deletions include/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#define DEFAULT_HEIGHT "720"
#define DEFAULT_TIME_ENABLED false

enum InputType { UNDEFINED_INPUT, FILE_INPUT, RTSP_INPUT, VIDEOTESTSRC_INPUT };

typedef struct s_config {
// Server config
std::string username;
Expand All @@ -38,6 +40,7 @@ typedef struct s_config {
std::string port;

// Input
InputType input_type;
std::string input;

// Encoding
Expand All @@ -61,13 +64,17 @@ typedef struct s_server {

void init(t_server *serv);
// Config
bool parse_args(std::shared_ptr<t_config> config, int argc, char **argv);
void parse_env(std::shared_ptr<t_config> config);
bool parse_args(std::shared_ptr<t_config> &config, int argc, char **argv);
void parse_env(std::shared_ptr<t_config> &config);
void parse_input_type(std::shared_ptr<t_config> &config);
std::string input_type_to_string(InputType type);
void dump_config(std::shared_ptr<t_config> &config);
// Server
void server_init(t_server *serv);
int server_launch(t_server *serv);
// Print
void print_logo();
void print_name();
// Pipeline
std::string create_pipeline(std::shared_ptr<t_config> config);
std::string create_pipeline(std::shared_ptr<t_config> &config);
void configure_file_input(t_server *serv);
Binary file modified samples/forgotten-object.mp4
Binary file not shown.
102 changes: 102 additions & 0 deletions src/file_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2016 Etix Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "server.h"
#include <cstdio>
#include <cstdlib>
#include <gst/app/gstappsink.h>
#include <gst/app/gstappsrc.h>
#include <iostream>
#include <string.h>

typedef struct _App App;
struct _App {
GstElement *videosink;
};
App s_app;

typedef struct {
App *app;
GstClockTime timestamp;
} Context;

// Receive data from videosink and push it downstream
static void need_data(GstElement *appsrc, guint unused, Context *ctx) {
GstFlowReturn ret;
GstSample *sample =
gst_app_sink_pull_sample(GST_APP_SINK(ctx->app->videosink));
if (sample != NULL) {
GstBuffer *buffer = gst_sample_get_buffer(sample);
gst_app_src_push_sample(GST_APP_SRC(appsrc), sample);
gst_sample_unref(sample);
GST_BUFFER_PTS(buffer) = ctx->timestamp;
GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale_int(1, GST_SECOND, 29);
ctx->timestamp += GST_BUFFER_DURATION(buffer);
g_signal_emit_by_name(appsrc, "push-buffer", buffer, &ret);
}
}

// Configure the media to push properly data
static void media_configure(GstRTSPMediaFactory *factory, GstRTSPMedia *media,
App *app) {
Context *ctx;
GstElement *pipeline;
GstElement *appsrc;
pipeline = gst_rtsp_media_get_element(media);
appsrc = gst_bin_get_by_name_recurse_up(GST_BIN(pipeline), "mysrc");
gst_util_set_object_arg(G_OBJECT(appsrc), "format", "time");
g_object_set(G_OBJECT(appsrc), "max-bytes",
gst_app_src_get_max_bytes(GST_APP_SRC(appsrc)), NULL);
ctx = g_new0(Context, 1);
ctx->app = app;
ctx->timestamp = 0;
g_signal_connect(appsrc, "need-data", (GCallback)need_data, ctx);
}

// Bus message handler
gboolean bus_callback(GstBus *bus, GstMessage *msg, gpointer data) {
GstElement *pipeline = GST_ELEMENT(data);

switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS: // Catch EOS to reset TS
if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
GST_SEEK_TYPE_SET, 1000000000, GST_SEEK_TYPE_NONE,
GST_CLOCK_TIME_NONE)) {
g_message("Seek failed!");
}
break;
default:
break;
}
return TRUE;
}

void configure_file_input(t_server *serv) {
// Setup and configuration
App *app = &s_app;
GstElement *playbin = gst_element_factory_make("playbin", "play");
app->videosink = gst_element_factory_make("appsink", "video_sink");
g_object_set(G_OBJECT(app->videosink), "emit-signals", FALSE, "sync", TRUE,
NULL);
g_object_set(G_OBJECT(playbin), "video-sink", app->videosink, NULL);
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(playbin));
gst_bus_add_watch(bus, bus_callback, playbin);
std::string input_path = "file:///" + serv->config->input;
g_object_set(G_OBJECT(playbin), "uri", input_path.c_str(), NULL);
gst_element_set_state(playbin, GST_STATE_PLAYING);

// Media
g_signal_connect(serv->factory, "media-configure", (GCallback)media_configure,
app);
}
18 changes: 1 addition & 17 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,6 @@ void signal_handler(int signal) {
exit(1);
}

void dump_config(std::shared_ptr<t_config> config) {
// Server config
std::cout << "Server configuration:" << std::endl
<< "Address:\t" << config->address << std::endl
<< "Port:\t\t" << config->port << std::endl
<< "Route:\t\t" << config->route << std::endl
<< "Username:\t" << config->username << std::endl
<< "Password:\t" << config->password << std::endl
<< std::endl;

// Input
std::cout << "Input:\t\t";
config->input.empty() ? std::cout << "pattern:smpte"
: std::cout << config->input;
std::cout << std::endl << std::endl;
}

int main(int argc, char **argv) {
std::signal(SIGINT, signal_handler);

Expand All @@ -54,6 +37,7 @@ int main(int argc, char **argv) {
std::cerr << "Unable to parse arguments" << std::endl;
return -1;
}
parse_input_type(config);

print_logo();
print_name();
Expand Down
50 changes: 48 additions & 2 deletions src/parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include <string.h>

// Set config defaults or from env
void parse_env(std::shared_ptr<t_config> config) {
void parse_env(std::shared_ptr<t_config> &config) {
// Address
config->address = DEFAULT_ADDRESS;
if (const char *address = std::getenv("RTSP_ADDRESS")) {
Expand Down Expand Up @@ -89,7 +89,7 @@ void parse_env(std::shared_ptr<t_config> config) {
}

// Overwrite default parameters via cmd line
bool parse_args(std::shared_ptr<t_config> config, int argc, char **argv) {
bool parse_args(std::shared_ptr<t_config> &config, int argc, char **argv) {
int c;
opterr = 0;
while ((c = getopt(argc, argv, "r:u:l:p:b:f:s:i:ht")) != -1) {
Expand Down Expand Up @@ -184,3 +184,49 @@ bool parse_args(std::shared_ptr<t_config> config, int argc, char **argv) {
}
return true;
}

void parse_input_type(std::shared_ptr<t_config> &config) {
if (config->input.compare(0, 7, "rtsp://") == 0) { // RTSP stream input
config->input_type = RTSP_INPUT;
} else if (config->input.empty() || config->input.compare(0, 8,
"pattern:") ==
0) { // Videotestsrc pattern input
config->input_type = VIDEOTESTSRC_INPUT;
} else { // File
config->input_type = FILE_INPUT;
}
}

std::string input_type_to_string(InputType type) {
switch (type) {
case UNDEFINED_INPUT:
return "undefined";
case FILE_INPUT:
return "file";
case RTSP_INPUT:
return "rtsp";
case VIDEOTESTSRC_INPUT:
return "videotestsrc";
default:
break;
}
}

void dump_config(std::shared_ptr<t_config> &config) {
// Server config
std::cout << "Server configuration:" << std::endl
<< "Address:\t" << config->address << std::endl
<< "Port:\t\t" << config->port << std::endl
<< "Route:\t\t" << config->route << std::endl
<< "Username:\t" << config->username << std::endl
<< "Password:\t" << config->password << std::endl
<< std::endl;

// Input
std::cout << "Input:\t\t";
config->input.empty() ? std::cout << "pattern:smpte" << std::endl
: std::cout << config->input << std::endl;
std::cout << "Input type:\t" << input_type_to_string(config->input_type)
<< std::endl
<< std::endl;
}
23 changes: 10 additions & 13 deletions src/pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include <string.h>

// If time overlay is enabled, add it to the pipeline
std::string time_overlay(std::shared_ptr<t_config> config) {
std::string time_overlay(std::shared_ptr<t_config> &config) {
if (config->time) {
return " ! timeoverlay halignment=left valignment=top "
"shaded-background=true "
Expand All @@ -32,7 +32,7 @@ std::string time_overlay(std::shared_ptr<t_config> config) {
}

// Take raw, change caps according to conf and transcode in h264
std::string encode(std::shared_ptr<t_config> config) {
std::string encode(std::shared_ptr<t_config> &config) {
std::cout << "H264 encoding with:" << std::endl
<< "Framerate:\t" << config->framerate << std::endl
<< "Resolution:\t" << config->scale.first << "x"
Expand All @@ -57,7 +57,7 @@ std::string encode(std::shared_ptr<t_config> config) {
}

// Rtsp input pipeline
std::string create_rtsp_input(std::shared_ptr<t_config> config) {
std::string create_rtsp_input(std::shared_ptr<t_config> &config) {
std::string launchCmd = "";

// Receive & depay
Expand All @@ -78,7 +78,7 @@ std::string create_rtsp_input(std::shared_ptr<t_config> config) {
}

// Videosrc input pipeline
std::string create_videotestsrc_input(std::shared_ptr<t_config> config) {
std::string create_videotestsrc_input(std::shared_ptr<t_config> &config) {
std::string launchCmd = "";

launchCmd += "videotestsrc ";
Expand All @@ -93,11 +93,10 @@ std::string create_videotestsrc_input(std::shared_ptr<t_config> config) {
}

// File input pipeline
std::string create_file_input(std::shared_ptr<t_config> config) {
std::string create_file_input(std::shared_ptr<t_config> &config) {
std::string launchCmd = "";

launchCmd += "multifilesrc loop=true location=";
launchCmd += config->input;
launchCmd += "appsrc name=mysrc";
launchCmd += " ! decodebin";

launchCmd += time_overlay(config);
Expand All @@ -106,16 +105,14 @@ std::string create_file_input(std::shared_ptr<t_config> config) {
}

/* Create pipeline according to config */
std::string create_pipeline(std::shared_ptr<t_config> config) {
std::string create_pipeline(std::shared_ptr<t_config> &config) {
std::string launchCmd = "( ";

if (config->input.compare(0, 7, "rtsp://") == 0) { // RTSP stream input
if (config->input_type == RTSP_INPUT) {
launchCmd += create_rtsp_input(config);
} else if (config->input.empty() || config->input.compare(0, 8,
"pattern:") ==
0) { // Videotestsrc pattern input
} else if (config->input_type == VIDEOTESTSRC_INPUT) {
launchCmd += create_videotestsrc_input(config);
} else { // File
} else if (config->input_type == FILE_INPUT) {
launchCmd += create_file_input(config);
}

Expand Down
3 changes: 3 additions & 0 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ void server_init(t_server *serv) {

/* Pipeline launch */
std::string launchCmd = create_pipeline(serv->config);
if (serv->config->input_type == FILE_INPUT) {
configure_file_input(serv);
}
g_print("Launching stream with the following pipeline: %s\n",
launchCmd.c_str());
gst_rtsp_media_factory_set_launch(serv->factory, launchCmd.c_str());
Expand Down

0 comments on commit 72325a7

Please sign in to comment.