├── Dockerfile ├── startup.sh ├── create_ffmpeg_cmd.sh ├── nginx └── nginx.conf └── README.md /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | 3 | # Install nginx and ffmpeg 4 | RUN apk add --update nginx ffmpeg && rm -rf /var/cache/apk/* && mkdir /tmp/stream 5 | COPY nginx/nginx.conf /etc/nginx/nginx.conf 6 | 7 | COPY ./startup.sh / 8 | COPY ./create_ffmpeg_cmd.sh / 9 | RUN ["chmod", "+x", "/startup.sh"] 10 | RUN ["chmod", "+x", "/create_ffmpeg_cmd.sh"] 11 | 12 | CMD ["/startup.sh"] 13 | -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | INPUT=$PARAMETERS 4 | 5 | COUNTER=0 6 | IN="" 7 | OUT="" 8 | for parameter in $INPUT 9 | do 10 | if [ $COUNTER -eq 0 ]; then 11 | IN=$parameter 12 | fi 13 | if [ $COUNTER -eq 1 ]; then 14 | OUT=$parameter 15 | echo "Starting ${OUT} stream" 16 | $(sh ./create_ffmpeg_cmd.sh ${IN} ${OUT}) & 17 | COUNTER=0 18 | IN="" 19 | OUT="" 20 | continue 21 | fi 22 | COUNTER=$((COUNTER+1)) 23 | done 24 | 25 | nginx -g "daemon off;" 26 | -------------------------------------------------------------------------------- /create_ffmpeg_cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | OUTPUT_PATH=/tmp/stream/ 4 | 5 | DEFAULT_AUDIO="copy" 6 | DEFAULT_VIDEO="copy" 7 | LACKING_AUDIO="" 8 | IS_RTSP="" 9 | 10 | INPUT=$1 11 | OUTPUT=$2 12 | 13 | # Check if codecs are already in the format supported by Chromecast devices (aac for audio and h264 for video) 14 | AUDIO_RESULT="$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 $INPUT)" 15 | VIDEO_RESULT="$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 $INPUT)" 16 | 17 | 18 | if [ "$AUDIO_RESULT" != "aac" ]; then 19 | DEFAULT_AUDIO=aac 20 | fi 21 | 22 | # Check if it's empty to add silent dummy stream 23 | if [ -z "$AUDIO_RESULT" ]; then 24 | LACKING_AUDIO="-f lavfi -i aevalsrc=0" 25 | fi 26 | 27 | if [ "$VIDEO_RESULT" != "h264" ]; then 28 | DEFAULT_VIDEO=h264 29 | fi 30 | 31 | if [ "$INPUT" == rtsp://* ]; then 32 | IS_RTSP="-rtsp_transport tcp" 33 | fi 34 | 35 | FFMPEG_CMD="ffmpeg ${IS_RTSP} -i ${INPUT} ${LACKING_AUDIO} -acodec ${DEFAULT_AUDIO} -vcodec ${DEFAULT_VIDEO} -hls_list_size 2 -hls_init_time 1 -hls_time 1 -hls_flags delete_segments ${OUTPUT_PATH}${OUTPUT}.m3u8" 36 | 37 | echo "${FFMPEG_CMD}" 38 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | sendfile off; 13 | tcp_nopush on; 14 | directio 512; 15 | default_type application/octet-stream; 16 | 17 | access_log /var/log/nginx/access.log; 18 | 19 | server { 20 | listen 80; 21 | 22 | location / { 23 | # Disable cache 24 | add_header 'Cache-Control' 'no-cache'; 25 | 26 | # CORS setup 27 | add_header 'Access-Control-Allow-Origin' '*' always; 28 | add_header 'Access-Control-Expose-Headers' 'Content-Length'; 29 | 30 | # allow CORS preflight requests 31 | if ($request_method = 'OPTIONS') { 32 | add_header 'Access-Control-Allow-Origin' '*'; 33 | add_header 'Access-Control-Max-Age' 1728000; 34 | add_header 'Content-Type' 'text/plain charset=UTF-8'; 35 | add_header 'Content-Length' 0; 36 | return 204; 37 | } 38 | 39 | types { 40 | application/dash+xml mpd; 41 | application/vnd.apple.mpegurl m3u8; 42 | video/mp2t ts; 43 | } 44 | 45 | root /tmp/stream; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Streamer 2 | 3 | This docker image will start a container that can take existing streams (e.g.: rtsp streams from your security cameras) and use FFMPEG to convert it into HLS streams and drop them in a folder that Nginx is serving. 4 | 5 | In order to cast an HLS stream to chromecast devices the server needs to contain certain headers (e.g.: CORS headers, correct content type) which is being done here in Nginx. 6 | (PS: Make sure you are comfortable with the `Access-Control-Allow-Origin *` header) 7 | 8 | The HLS stream that chromecast accepts has some additional restrictions, such as limited audio and video codec support. This image will check if the codecs are AAC and h264 for audio and video, if they are are it will use the copy command in ffmpeg (low cpu usage) if not it will transcode the original stream so that the chromecast can stream it. 9 | 10 | Chromecast devices also don't seem to support HLS streams without an audio track, this image will also generate a dummy silent audio track if your original stream is lacking sound (Not uncommon for security cameras). 11 | 12 | ### Running 13 | 14 | #### Build the image 15 | (optional) 16 | 17 | `docker build . -t gihad/streamer` 18 | 19 | #### Run with docker 20 | Need to expose the port, mount the volume and pass in parameters 21 | 22 | `docker run -e PARAMETERS="INPUT_STREAM_1 STREAM_1_NAME INPUT_STREAM_2 STREAM_2_NAME" -v host_volume:container_volume -p host_port:container_port gihad/streamer` 23 | 24 | Example: 25 | 26 | `docker run -e PARAMETERS="rtsp://username:password@192.168.1.183:554/cam/realmonitor?channel=1&subtype=1 frontyard rtsp://username:password@192.168.1.183:554/cam/realmonitor?channel=2&subtype=1 driveway" -v /tmp/stream:/tmp/stream -p 8080:80 gihad/streamer` 27 | 28 | ### Paramaters format 29 | The parameters need to be passed in as a single environment variable called PARAMETERS separated by spaces in the following format: 30 | 31 | `INPUT_STREAM_1 STREAM_1_NAME INPUT_STREAM_2 STREAM_2_NAME` 32 | 33 | Input followed by stream name. In the example above 2 streams will be created but the program supports more than 2. 34 | 35 | ### Accessing the stream(s) 36 | 37 | The streams will be accessible on port 8080 and the resource will be the chosen stream name suffixed with .m3u8. 38 | 39 | Example: if the address of the host is `192.168.1.100` and the name of the stream passed as a parameter was `driveway` you will find the HLS steam at `http://192.168.1.100:8080/driveway.m3u8` 40 | 41 | ### Tips 42 | 43 | I was able to run multiple HLS streams on a Raspberry Pi 1, in order to that I needed to set the input stream from the cameras to use AAC and h264, in this case the container running ffmpeg could just use the `copy` command for the codecs instead of having to transcode it. 44 | --------------------------------------------------------------------------------