Skip to content

Latest commit

 

History

32 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Eyevinn Live Encoding

Open Source Live Encoder based on ffmpeg and Shaka packager.

📖 Available as a Service 👀

PRs welcome made with hearth by Eyevinn Slack

Badge OSC

Live transcoding to HLS and optionally MPEG-DASH. Provides origin for CDN shield to pull streams as well as push to CDN origin.

Screenshot 1 Screenshot 2

Badge OSC

Requirements

  • ffmpeg and optionally Shaka packager installed

Installation / Usage

% npm install

Environment Variables

Variable Description Default value
PORT API port to bind and listen to 8000
ORIGIN_DIR Location on disk where to write media segments and playlists /tmp/media
HLS_ONLY Only output HLS + TS true
RTMP_PORT RTMP port to bind and listen to 1935
STREAM_KEY RTMP streamkey stream
INPUT_URL Optional srt:// input URL. If set, the encoder dials this source in caller mode instead of listening for an RTMP publisher. If not set the RTMP listener is used
INPUT_DIAL_TIMEOUT Caller-mode dial deadline in seconds. If the source is never reached within this bound the encoder gives up and goes to error. A per-request timeout overrides it 300
OUTPUT_URL URL to upload media segments and playlists. If not set push to CDN is disabled
LADDER ABR ladder as a comma-separated list of video rungs, each <width>x<height>:<bitrate>, e.g. 1920x1080:5000k,1280x720:2800k,640x360:800k. An invalid value fails startup. If not set the built-in default ladder is used 1280x720:4M,640x360:3M
FRAMERATE Output framerate as a positive integer (typically 25, 30, 50 or 60). Each rung is converted to this rate and the GOP is set to 2 x framerate. If not set the output follows the input framerate input framerate
RATE_CONTROL Per-rung H.264 rate-control mode: cbr (strict constant bitrate) or capped-vbr (VBV-capped variable bitrate). An invalid value fails startup cbr
MAXRATE_FACTOR Positive float. Under capped-vbr the per-rung -maxrate is round(MAXRATE_FACTOR x target bitrate). Ignored under cbr 1.15
BUFSIZE_FACTOR Positive float. Under capped-vbr the per-rung -bufsize is round(BUFSIZE_FACTOR x maxrate). Ignored under cbr 2.0
SEGMENT_DURATION HLS segment duration in seconds, passed to -hls_time. A positive integer of at most 60. An invalid value fails startup. If not set the historical 10 is used 10
SEGMENT_TYPE HLS segment container, passed to -hls_segment_type: mpegts (MPEG-TS .ts segments) or fmp4 (an init segment plus fragmented-MP4 .m4s segments). An invalid value fails startup mpegts
PROGRAM_DATE_TIME Set to true or 1 to write an #EXT-X-PROGRAM-DATE-TIME tag for every segment in the media playlists, mapping each segment to wall-clock time false
SUBTITLE_URL Sidecar WebVTT source URL fetched alongside the A/V input. If not set the output stays video+audio only
SUBTITLE_LANGUAGE BCP-47 language tag for the subtitle rendition, e.g. en und
SUBTITLE_NAME Display name for the subtitle rendition, e.g. English value of SUBTITLE_LANGUAGE
SUBTITLE_DEFAULT Whether the subtitle rendition is the default (true/1) false

Subtitles

When SUBTITLE_URL is set to an http(s) WebVTT source, the encoder fetches it alongside the A/V input (RTMP listener or SRT caller) and publishes it as a segmented WebVTT rendition referenced from the HLS master playlist as an #EXT-X-MEDIA:TYPE=SUBTITLES group, available across the whole ABR ladder.

% ORIGIN_DIR=/data \
  SUBTITLE_URL=https://example.com/subtitles/en.vtt \
  SUBTITLE_LANGUAGE=en \
  SUBTITLE_NAME=English \
  SUBTITLE_DEFAULT=true \
  npm start

ABR ladder and framerate

The ABR ladder and the output framerate are configurable through the environment. Both are optional: with neither set the encoder emits its built-in default ladder at the input framerate, exactly as before.

Set LADDER to a comma-separated list of video rungs, each <width>x<height>:<bitrate>:

% ORIGIN_DIR=/data \
  LADDER='1920x1080:5000k,1280x720:2800k,640x360:800k' \
  npm start

The bitrate of each rung is passed to ffmpeg verbatim, so an integer with an optional k/M/G suffix is accepted (5000k, 5M, 5000000). An invalid LADDER value fails startup with an error naming the offending entry: the encoder never silently falls back to the default ladder, because an operator who set LADDER expects that exact ladder and a quietly-different stream is harder to diagnose than a startup crash. Audio is not configurable through LADDER today; the default stereo AAC rung is always appended.

Set FRAMERATE to a positive integer to convert every rung to that framerate:

% ORIGIN_DIR=/data \
  LADDER='1280x720:2800k,640x360:800k' \
  FRAMERATE=50 \
  npm start

With FRAMERATE set, the keyframe interval (GOP) is derived as 2 x framerate so segments stay keyframe-aligned at a ~2 s cadence. With FRAMERATE unset the output follows the input framerate and the GOP stays at the fixed default of 48, so setting LADDER alone does not change the framerate or GOP.

Rate control

RATE_CONTROL selects the per-rung H.264 rate-control mode. The default is cbr, which keeps the historical strict constant-bitrate encode: -b:v, -maxrate, -minrate and -bufsize are all set to the rung's target bitrate and the x264 nal-hrd=cbr HRD model is enabled.

Set RATE_CONTROL=capped-vbr for a VBV-capped variable bitrate instead. Each rung keeps its LADDER bitrate as the average target, but the peak is capped at -maxrate with a VBV buffer of -bufsize, -minrate is dropped and the nal-hrd=cbr param is removed (force-cfr=1 is kept so the output frame rate stays constant). This lets simpler scenes spend fewer bits while still bounding the peak for ABR delivery.

% ORIGIN_DIR=/data \
  LADDER='1920x1080:5000k,1280x720:2800k,640x360:800k' \
  RATE_CONTROL=capped-vbr \
  npm start

The peak and buffer are derived from two documented constants, overridable per deployment:

  • -maxrate = round(MAXRATE_FACTOR x target), MAXRATE_FACTOR defaulting to 1.15 (15% peak headroom over the target)
  • -bufsize = round(BUFSIZE_FACTOR x maxrate), BUFSIZE_FACTOR defaulting to 2.0

Both factors are positive floats and are only consulted under capped-vbr; they are ignored under cbr. An invalid RATE_CONTROL, MAXRATE_FACTOR or BUFSIZE_FACTOR value fails startup with an error naming the offending value, rather than silently falling back, for the same reason as LADDER and FRAMERATE.

Segment duration

Set SEGMENT_DURATION to a positive integer number of seconds to change the HLS segment duration (-hls_time) from the default 10. Shorter segments reduce end-to-end latency at the cost of more HTTP requests and playlist churn; typical live values are 4, 6 or 10.

% ORIGIN_DIR=/data \
  FRAMERATE=50 \
  SEGMENT_DURATION=4 \
  npm start

ffmpeg's HLS muxer cuts a segment at the first keyframe at or after the requested duration, so actual segment lengths round up to the keyframe cadence. With FRAMERATE set the GOP is 2 x framerate (a 2 s keyframe cadence), meaning an even SEGMENT_DURATION is hit exactly while an odd one is extended to the next keyframe (SEGMENT_DURATION=5 yields ~6 s segments). This is valid HLS, so an odd value is accepted with a startup warning rather than rejected. Values above 60 fail startup: with the fixed playlist window of 6 segments they are far outside sensible HLS practice and almost certainly a unit mistake. An invalid value fails startup with an error naming the offending value, for the same reason as LADDER and FRAMERATE.

Segment container (MPEG-TS or fMP4/CMAF)

Set SEGMENT_TYPE=fmp4 to emit fragmented MP4 instead of the default MPEG-TS. The HLS output then consists of one initialisation segment per rendition (init_0.mp4, init_1.mp4, ...) referenced from each media playlist as #EXT-X-MAP, plus .m4s media fragments.

% ORIGIN_DIR=/data \
  FRAMERATE=50 \
  SEGMENT_TYPE=fmp4 \
  SEGMENT_DURATION=4 \
  npm start

The initialisation segment is named init.mp4 for a single-rung ladder and init_%v.mp4 (expanded per variant, e.g. init_0.mp4) for a multi-rung one. ffmpeg only expands %v when there is more than one variant stream, so a fixed init_%v.mp4 template would leave a single-rung ladder serving a file called literally init_%v.mp4, with that name in #EXT-X-MAP, where the percent sign reads as a percent-encoding escape.

Fragmented MP4 is the CMAF-style layout, which matters when the output is consumed by something other than a plain HLS player: MSE-based players, CMAF packagers, ISO BMFF tooling (mp4ff, MP4Box), and anything that needs to read or write box structure per segment. It is also format-shared with DASH, so the same segments can later be described by a DASH manifest without re-segmenting. MPEG-TS remains the default, so existing deployments are unaffected.

SEGMENT_TYPE only reaches the HLS muxer. With HLS_ONLY=false it is ignored, and the encoder logs a warning at startup rather than failing.

SEGMENT_TYPE=fmp4 is not carried by the CDN push. The OUTPUT_URL pull-push path uploads and rewrites media segment URIs only; it has no handling for the fMP4 initialisation segment, so the pushed playlists keep an #EXT-X-MAP pointing at this encoder's local origin and the init file is never delivered, leaving the pushed stream undecodable. Use the local /origin output for fMP4, or deliver the init segment by another route. The encoder logs a warning at startup when both are set.

Program date time

Set PROGRAM_DATE_TIME=true to add an #EXT-X-PROGRAM-DATE-TIME tag to every segment in the media playlists (ffmpeg's program_date_time HLS flag). Each segment then carries the wall-clock time of its first sample, which is what a player needs for a date-based seek, and what downstream tooling needs to correlate a segment with events recorded outside the stream.

% ORIGIN_DIR=/data \
  PROGRAM_DATE_TIME=true \
  npm start

The timestamps come from the encoder host's clock, so they are only as accurate as that clock: keep the host on NTP if anything downstream depends on them. This is independent of SEGMENT_TYPE and applies to MPEG-TS and fMP4 output alike. It defaults to off, leaving the playlists byte-identical to previous releases.

CDN Pull

Run encoder with media dir at /data

% ORIGIN_DIR=/data npm start

SRT input (caller mode)

By default the encoder listens for an incoming RTMP publisher. Set INPUT_URL to an srt:// URL to make the encoder dial a listener-mode SRT source in caller mode and pull the feed instead. This is useful for SRT contribution and for container platforms where no inbound UDP port can be exposed.

% ORIGIN_DIR=/data \
  INPUT_URL='srt://<host>:<port>?latency=200000&passphrase=<secret>&streamid=<id>' \
  npm start

Protocol knobs such as latency (microseconds), passphrase, streamid and connect_timeout (milliseconds) travel as query parameters on the URL and are handled by ffmpeg's srt reader directly, so any option the ffmpeg build supports can be used. While INPUT_URL is set the encoder stays in starting and retries the connection if the source is not up yet. It gives up and goes to error once the dial deadline is reached: the per-request timeout if supplied, otherwise INPUT_DIAL_TIMEOUT (default 300 seconds).

User Interface

Web user interface available at http://localhost:8000/

API

Start encoder:

% curl -X 'POST' \
  'http://localhost:8000/api/encoder' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "timeout": 0
}'

Get status:

% curl -X 'GET' \
  'http://localhost:8000/api/encoder' \
  -H 'accept: application/json'

If status is starting you can start pushing to the RTMP address rtmp://<your-host-ip>:1935/live/stream (where stream is the streamkey).

When status is running you can play the HLS from http://localhost:8000/origin/hls/index.m3u8

Top stop the encoder:

% curl -X 'DELETE' \
  'http://localhost:8000/api/encoder' \
  -H 'accept: application/json'

CDN Push (AWS Media Package)

Run encoder with media dir at /data

% ORIGIN_DIR=/data \
  OUTPUT_URL=https://<username>>:<password>@xxxxx.mediapackage.xxxx.amazonaws.com/in/v2/e82a0fc53d4b44ec89ac1a1fccd3a333/e82a0fc53d4b44ec89ac1a1fccd3a333/channel \
  npm start

Docker

Run Eyevinn live encoding as a Docker container where /tmp/media is a directory on your host.

% docker run --rm -d \
  -p 8000:8000 -p 1935:1935 \
  -v /tmp/media:/data \
  eyevinntechnology/live-encoding

Development

Start the API in development mode that restart server if file changes.

% DEBUG=1 npm run dev

API is then available at http://localhost:8000/api

Start the web application in development mode

% npm run dev:app

Then the web application is available at http://localhost:3000/ and will connect to the API on port 8000.

To then build the app run:

% npm run build:app

The output is placed in the folder out/ that is then served by the server.

Contributing

See CONTRIBUTING

License

This project is licensed under the MIT License, see LICENSE.

Support

Join our community on Slack where you can post any questions regarding any of our open source projects. Eyevinn's consulting business can also offer you:

  • Further development of this component
  • Customization and integration of this component into your platform
  • Support and maintenance agreement

Contact sales@eyevinn.se if you are interested.

About Eyevinn Technology

Eyevinn Technology is an independent consultant firm specialized in video and streaming. Independent in a way that we are not commercially tied to any platform or technology vendor. As our way to innovate and push the industry forward we develop proof-of-concepts and tools. The things we learn and the code we write we share with the industry in blogs and by open sourcing the code we have written.

Want to know more about Eyevinn and how it is to work here. Contact us at work@eyevinn.se!

About

Open Source Live encoder based on ffmpeg

Resources

Contributing

Stars

6 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages