From 067ed91934b3979ee4643fbbb920ca590d63fb75 Mon Sep 17 00:00:00 2001 From: Gegonz Date: Mon, 29 May 2017 09:08:53 +0200 Subject: [PATCH] Full containerization of workflow (#14) * Tweak CES and run it easily inside docker * Code refacto * Override CES defaults only through env * Catch signals (to stop container) * Fix port issue binding * Fix typo in README * Update binary and container name to ces * Update README to move override options section * Update gitignore for new binary name --- .gitignore | 2 +- CMakeLists.txt | 25 +++- Dockerfile | 4 +- README.md | 62 +++++----- build.sh | 7 +- build_image/build_ces_inside_docker.sh | 2 +- include/server.h | 18 ++- src/main.cpp | 36 ++++++ src/parsing.cpp | 73 ++++++++++++ src/server.cpp | 158 +------------------------ 10 files changed, 186 insertions(+), 201 deletions(-) create mode 100644 src/main.cpp create mode 100644 src/parsing.cpp diff --git a/.gitignore b/.gitignore index 7b1f668..281ef21 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,4 @@ # Build artifacts build/ -camera_emulation_server \ No newline at end of file +ces \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c545bea..356e486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,13 +14,28 @@ # min config cmake_minimum_required(VERSION 2.8.1) - cmake_policy(SET CMP0042 NEW) -set(PROJECT_NAME camera_emulation_server) +set(PROJECT_NAME ces) project(${PROJECT_NAME}) +# function to retrieve files sources inside a list of folders +function(find_sources DIRS) + set(_headers "") + set(_srcs "") + foreach (dir ${ARGV}) + file (GLOB_RECURSE h_${dir} "${dir}/*.h" "${dir}/*.ipp" "${dir}/*.hpp") + file (GLOB_RECURSE s_${dir} "${dir}/*.cpp" "${dir}/*.c" "${dir}/*.cxx") + source_group (${dir} FILES ${s_${dir}} ${h_${dir}}) + set (_srcs ${_srcs} ${s_${dir}}) + set (_headers ${_headers} ${h_${dir}}) + endforeach () + set (SOURCES ${_srcs} PARENT_SCOPE) + set (HEADERS ${_headers} PARENT_SCOPE) +endfunction() + + # Cmake properties set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) @@ -64,6 +79,7 @@ include_directories ( "${GST_RTSP_SERVER_INCLUDE_DIRS}" ) + link_directories ( "${GSTREAMER_LIBRARY_DIRS}" "${GST_RTSP_SERVER_LIBRARY_DIRS}" @@ -79,6 +95,7 @@ if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") link_directories (${GSTREAMER_APP_LIBRARY_DIRS}) endif() -add_executable(camera_emulation_server "src/server.cpp") +find_sources("src" "include") +add_executable (ces ${HEADERS} ${SOURCES}) -target_link_libraries (camera_emulation_server ${GSTREAMER_LIBRARIES} ${GST_RTSP_SERVER_LIBRARY_DIRS}) +target_link_libraries (ces ${GSTREAMER_LIBRARIES} ${GST_RTSP_SERVER_LIBRARY_DIRS}) diff --git a/Dockerfile b/Dockerfile index fa4f702..1924c8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,6 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ apt-get clean &&\ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -ADD camera_emulation_server / +ADD ces / EXPOSE 8554 -ENTRYPOINT ["/camera_emulation_server"] +ENTRYPOINT ["/ces"] diff --git a/README.md b/README.md index c1cc364..ba7481e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# CES : Camera Emulation Server 1.0.0 +# CES : Camera Emulation Server 1.1.0 [![License](https://img.shields.io/badge/license-Apache-blue.svg)](#license) -[![Latest release](https://img.shields.io/badge/release-1.0.0-green.svg)](https://github.com/EtixLabs/CES/releases/latest) +[![Latest release](https://img.shields.io/badge/release-1.1.0-green.svg)](https://github.com/EtixLabs/CES/releases/latest) ### RTSP server with authentication for testing purposes @@ -9,47 +9,51 @@ * `docker` -## Examples of use +## Usage from the official docker repository -To create a simple test stream, just launch the following command: -`docker run -p 8554:8554 ullaakut/CES` +You can create a stream by launching the official docker image: +`docker run --rm -p 8554:8554 ullaakut/ces` -You can now access it on the URL `rtsp://0.0.0.0:8554/live.sdp`. +With default options, stream will be available at `rtsp://0.0.0.0:8554/live.sdp` +You can use [override options](#override-options) -To create a test stream with credentials, just add the RTSP_PASSWORD and RTSP_USERNAME arguments like so: - -`docker run -p 8554:8554 -e RTSP_PASSWORD=mypass -e RTSP_USERNAME=myusername` - -You can now access it on the URL `rtsp://myusername:mypass@0.0.0.0:8554/live.sdp`. - -### Usage +## Override options ``` -docker run \ - [-e RTSP_LISTEN_ADDRESS=your_listen_address] \ +docker run --rm \ + [-e RTSP_ADDRESS=your_address] \ [-e RTSP_PORT=your_port] -p your_port:your_port \ - [-e RTSP_PATH=your_path] \ + [-e RTSP_ROUTE=your_route] \ [-e RTSP_INPUT_FILE=your_input_file] \ [-e RTSP_USERNAME=your_username] \ [-e RTSP_PASSWORD=your_password] \ [-e RTSP_RESOLUTION='your_width'x'your_height'] \ [-e RTSP_FRAMERATE=your_framerate] \ [-e GST_DEBUG=your_debug_level] \ - ullaakut/CES + ullaakut/ces ``` -### Parameters - -All of these options override the default parameters for CES -* `your_listen_address`: The address you want your server to listen on [default: `0.0.0.0`] -* `your_port`: The port that you want your server to listen on [default: `8554`] _Don't forget to also expose the port in your container with the -p option like in the example above_ -* `your_path`: The rtsp path at which you want your stream to be served [default: `/live.sdp`] -* `your_input_file`: The video file you want to broadcast using CES [default: none] -* `your_username`: If you want to enable security on your stream, using this option will allow you to specify the username required to access your stream [default: none] -* `your_password`: If you want to enable security on your stream, using this option will allow you to specify the password required to access your stream [default: none] -* `'your_width'x'your_height'`: The resolution at which you want to stream [default: `352x288`] -* `your_framerate`: The desired output framerate for your stream [default: `25`] -* `your_debug_level`: The desired debug level for GStreamer [default: none] _See [this link](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gst-running.html) for more information on this variable_ +All of these environment variables override the default parameters for CES +* `RTSP_ADDRESS`: The address you want your server to listen on [default: `0.0.0.0`] +* `RTSP_PORT`: The port that you want your server to listen on [default: `8554`] _Don't forget to also expose the port in your container with the -p option like in the example above_ +* `RTSP_ROUTE`: The rtsp route at which you want your stream to be served [default: `/live.sdp`] +* `RTSP_INPUT_FILE`: The video file you want to broadcast using CES [default: none] +* `RTSP_USERNAME`: If you want to enable security on your stream, using this option will allow you to specify the username required to access your stream [default: none] +* `RTSP_PASSWORD`: If you want to enable security on your stream, using this option will allow you to specify the password required to access your stream [default: none] +* `RTSP_RESOLUTION`: The resolution at which you want to stream [default: `352x288`] +* `RTSP_FRAMERATE`: The desired output framerate for your stream [default: `25`] +* `GST_DEBUG`: The desired debug level for GStreamer [default: none] _See [this link](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gst-running.html) for more information on this variable_ + +## Build and tweak yourself + +You can tweak CES and create your own docker image. For this simply run: +`./build.sh` + +Then launch it with: +`docker run --rm -p 8554:8554 ces` + +With default options, stream will be available at `rtsp://0.0.0.0:8554/live.sdp` +You can use [override options](#override-options) ## License diff --git a/build.sh b/build.sh index c35c633..ac1a095 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,11 @@ set -e set -x -rm -f camera_emulation_server +rm -f ces + +# Build binary docker build -f build_image/Dockerfile-build -t "build-ces" . docker run --rm -v $PWD:/tmp/CES -w/tmp/CES build-ces + +# Build image +docker build -t "ces" . diff --git a/build_image/build_ces_inside_docker.sh b/build_image/build_ces_inside_docker.sh index d27c8b8..1f17d60 100755 --- a/build_image/build_ces_inside_docker.sh +++ b/build_image/build_ces_inside_docker.sh @@ -10,4 +10,4 @@ cmake ../CES make # mv CES bin to mounted dir -mv camera_emulation_server ../CES +mv ces ../CES diff --git a/include/server.h b/include/server.h index 325554d..90d9d54 100644 --- a/include/server.h +++ b/include/server.h @@ -14,16 +14,8 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include typedef struct s_config { gchar *username; @@ -44,5 +36,11 @@ typedef struct s_server { GstRTSPAuth *auth; GstRTSPToken *token; gchar *basic; - std::unique_ptr config; + std::shared_ptr config; } t_server; + +void init(t_server *serv); +int parse_args(std::shared_ptr config, int argc, char **argv); +void parse_env(std::shared_ptr config); +void init_server_auth(t_server *serv); +int server_launch(t_server *serv); diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b4046ab --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,36 @@ +// 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 +#include + +void signal_handler(int signal) { + std::cout << "Signal " << std::to_string(signal) << " catched" << std::endl; + exit(1); +} + +int main(int argc, char **argv) { + std::signal(SIGINT, signal_handler); + + /* Config parsing from env */ + t_server serv; + std::shared_ptr config = std::make_shared(); + parse_env(config); + serv.config = config; + + gst_init(NULL, NULL); + init_server_auth(&serv); + return server_launch(&serv); +} diff --git a/src/parsing.cpp b/src/parsing.cpp new file mode 100644 index 0000000..e7474d3 --- /dev/null +++ b/src/parsing.cpp @@ -0,0 +1,73 @@ +// 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 + +void parse_env(std::shared_ptr config) { + if (const char *route = std::getenv("RTSP_PATH")) + config->route = strdup(route); + else + config->route = strdup("/live.sdp"); + + if (const char *username = std::getenv("RTSP_USERNAME")) + config->username = strdup(username); + else + config->username = strdup(""); + + if (const char *password = std::getenv("RTSP_PASSWORD")) + config->password = strdup(password); + else + config->password = strdup(""); + + if (const char *input = std::getenv("RTSP_INPUT_FILE")) + config->input = strdup(input); + else + config->input = strdup(""); + + if (const char *scale = std::getenv("RTSP_RESOLUTION")) { + size_t pos = 0; + std::string scale_str(scale); + + if ((pos = scale_str.find("x")) == std::string::npos) { + fprintf(stderr, "No x token found between width and height in the scale " + "argument: %s\nUsing default values instead", + scale); + config->scale = + std::make_pair(strdup("352"), strdup("288")); + } else { + config->scale = std::make_pair( + strdup(scale_str.substr(0, pos).c_str()), + strdup(scale_str.substr(pos + 1).c_str())); + } + } else { + config->scale = + std::make_pair(strdup("352"), strdup("288")); + } + + if (const char *framerate = std::getenv("RTSP_FRAMERATE")) + config->framerate = strdup(framerate); + else + config->framerate = strdup("25"); + + if (const char *address = std::getenv("RTSP_ADDRESS")) + config->address = strdup(address); + else + config->address = strdup("0.0.0.0"); + + if (const char *port = std::getenv("RTSP_PORT")) + config->port = strdup(port); + else + config->port = strdup("8554"); +} diff --git a/src/server.cpp b/src/server.cpp index 9f34429..f470c4e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include "server.h" +#include +#include +#include static gboolean remove_func(GstRTSPSessionPool *pool, GstRTSPSession *session, GstRTSPServer *server) { @@ -40,67 +43,6 @@ static gboolean timeout(GstRTSPServer *server) { return true; } -void init(t_server *serv) { - /* Set default values for our config */ - serv->config = std::make_unique(); - - if (const char *route = std::getenv("RTSP_PATH")) - serv->config->route = strdup(route); - else - serv->config->route = strdup("/live.sdp"); - - if (const char *username = std::getenv("RTSP_USERNAME")) - serv->config->username = strdup(username); - else - serv->config->username = strdup(""); - - if (const char *password = std::getenv("RTSP_PASSWORD")) - serv->config->password = strdup(password); - else - serv->config->password = strdup(""); - - if (const char *input = std::getenv("RTSP_INPUT_FILE")) - serv->config->input = strdup(input); - else - serv->config->input = strdup(""); - - if (const char *scale = std::getenv("RTSP_RESOLUTION")) { - size_t pos = 0; - std::string scale_str(scale); - - if ((pos = scale_str.find("x")) == std::string::npos) { - fprintf(stderr, - "No x token found between width and height in the scale " - "argument: %s\nUsing default values instead", - scale); - serv->config->scale = - std::make_pair(strdup("352"), strdup("288")); - } else { - serv->config->scale = std::make_pair( - strdup(scale_str.substr(0, pos).c_str()), - strdup(scale_str.substr(pos + 1).c_str())); - } - } else { - serv->config->scale = - std::make_pair(strdup("352"), strdup("288")); - } - - if (const char *framerate = std::getenv("RTSP_FRAMERATE")) - serv->config->framerate = strdup(framerate); - else - serv->config->framerate = strdup("25"); - - if (const char *address = std::getenv("RTSP_LISTEN_ADDRESS")) - serv->config->address = strdup(address); - else - serv->config->address = strdup("0.0.0.0"); - - if (const char *port = std::getenv("RTSP_LISTEN_PORT")) - serv->config->port = strdup(port); - else - serv->config->port = strdup("8554"); -} - void init_server_auth(t_server *serv) { serv->loop = g_main_loop_new(NULL, FALSE); @@ -115,7 +57,7 @@ void init_server_auth(t_server *serv) { gst_rtsp_server_set_address(serv->server, serv->config->address); gst_rtsp_server_set_service(serv->server, serv->config->port); - auto &&session = gst_rtsp_session_new("WESH"); + auto &&session = gst_rtsp_session_new("New session"); gst_rtsp_session_prevent_expire(session); /* make a media factory for a test stream. The default media factory can use @@ -211,93 +153,3 @@ failed : { } return 0; } - -int main(int argc, char *argv[]) { - t_server serv; - int c; - - gst_init(NULL, NULL); - init(&serv); - - opterr = 0; - while ((c = getopt(argc, argv, "r:u:l:p:i:b:f:s:h")) != -1) - switch (c) { - case 'r': // Route - if (optarg && optarg[0] == '-') - break; - if (optarg[0] == '/') - serv.config->route = strdup(optarg); - else - serv.config->route = strcat(strdup("/"), strdup(optarg)); - break; - case 'u': // Username - if (optarg && optarg[0] == '-') - break; - serv.config->username = strdup(optarg); - break; - case 'p': // Password - if (optarg && optarg[0] == '-') - break; - serv.config->password = strdup(optarg); - break; - case 'i': // Input - if (optarg && optarg[0] == '-') - break; - serv.config->input = strdup(optarg); - break; - case 'l': // Listen address - if (optarg && optarg[0] == '-') - break; - serv.config->address = strdup(optarg); - break; - case 'b': // Port - if (optarg && optarg[0] == '-') - break; - serv.config->port = strdup(optarg); - break; - case 'f': // Framerate - if (optarg && optarg[0] == '-') - break; - serv.config->framerate = strdup(optarg); - break; - case 's': { // Scale - if (optarg && optarg[0] == '-') - break; - size_t pos = 0; - std::string scale = optarg; - if ((pos = scale.find("x")) == std::string::npos) { - fprintf(stderr, - "No x token found between width and height in the scale " - "argument: %s\n", - optarg); - return -1; - } - serv.config->scale.first = strdup(scale.substr(0, pos).c_str()); - serv.config->scale.second = strdup(scale.substr(pos + 1).c_str()); - break; - } - case 'h': // help - fprintf(stdout, - "Usage: %s [-l address] [-b port] [-r route] [-i " - "input] [-u username] [-p password] [-f framerate] [-s " - "'width'x'height'] [-h]\n", - argv[0]); - return 0; - break; - case '?': - if (optopt == 'r' || optopt == 'l' || optopt == 'p' || optopt == 'u' || - optopt == 'i' || optopt == 'a' || optopt == 'b' || optopt == 'f' || - optopt == 's') - fprintf(stderr, "Option -%c requires an argument.\n", optopt); - else if (isprint(optopt)) - fprintf(stderr, "Unknown option `-%c'.\n", optopt); - else - fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); - return 1; - default: - abort(); - } - init_server_auth(&serv); - - return server_launch(&serv); -}