shavit

shavit

Video Streaming with Elixir (wiki)

To transcode the video there is FFMPEG.

On Demand

When a user uploads a video, the app renames and copy the file to a path, then call FFMPEG to transcode the video. Let’s say we are using HLS, we can use a script like this, to transcode the video to multiple resolutions, create a playlist file and sequences:
https://github.com/shavit/Diana/blob/master/bin/create_sequence

When a user requests a video, the server responds with m3u8 file, which contains a list of ts files. The creation of the sequence files was done by FFMPEG.

Example:

get "/video/:slug" do
ext = slug
  |> String.split(".")
|> List.last

video_file = cond do
  ext == "m3u8" -> "tmp/ts/320x180.m3u8"
  ext == "ts" -> "tmp/ts/#{slug}"
  # Default playlist file
  true -> "tmp/ts/320x180.m3u8"
end

file_path = Path.join(System.cwd, video_file)
offset = get_offset(conn.req_headers)
size = get_file_size(file_path)
   
conn
|> put_resp_content_type("application/vnd.apple.mpegurl")
|> put_resp_header("Accept-Ranges", "bytes")
|> put_resp_header("Content-Length", "#{size}")
|> put_resp_header("Content-Range", "bytes #{offset}-#{size-1}/#{size}")
|> send_file(206, file_path, offset, size-offset)

end

The Content-Range gives the video player the option to seek in the video.

Live video

When a client broadcast the stream, the server should transcode the video on the fly to multiple resolutions and file types. This can be done with FFMPEG too.

To accept the live video, the app can listen to UDP connections in a separate process.

{:ok, _socket} = :gen_udp.open(3001, [:binary, {:active, true}])

When a client request a live stream, the respond should block and wait for data. I started to write named pipes but Elixir couldn’t read the fifos. It seems that it should be done using agents.

Elixir Media Libs

Open source elixir libraries for working with media (currently focused on RTMP). (Dedicated thread is here)

Github: https://github.com/KallDrexx/elixir-media-libs

Hex Packages:

  • amf0 - Library with functions for serializing and deserializing data in the AMF0 encoding format. It’s not 100% complete on the spec but it has the core types implemented.
  • rtmp_handshake - Library that allows systems to perform an RTMP handshake, both as client and a server. It supports both simple and digest handshake formats.
  • rtmp_session - An abstraction that represents a single peer in an RTMP connection. This is essentially the core brain of an RTMP peer where you give it TCP packets and it spits out events and tcp responses. Right now it is only programmed to work as a server, but the client portion is on my todo list.
  • gen_rtmp_server - Behavior that makes easy to create your own RTMP server and provides callbacks for your own custom application logic to respond to RTMP events

Most Liked

KallDrexx

KallDrexx

In a semi related note, I am 90% ready to release my rtmp abstraction libraries, a GenRtmpServer abstraction, and a (very) simple rtmp server implementation all in Elixir.

Unfortunately, playback video is artifacting which is the only hold up, and I’m trying to determine where the issue lies (I don’t believe it’s Elixir related but instead an issue with … well I’m still trying to figure it out). Once I can get playback working properly I plan to split the umbrella project out, put it on GitHub, and create hex packages for the different pieces.

goofansu

goofansu

Membrane is out, you can have an eye on it.

Thread: Membrane - a new framework for multimedia processing

shavit

shavit

I tried to use the Elixir FFMPEG library, but it seems that it doesn’t support pipes at the moment.

While it still in development, I came up with the following script to write incoming live HLS stream into disk.

#!/bin/sh

DIR=`dirname $BASH_SOURCE[0]`
TMP_DIR="${DIR}/../tmp/pool"
OUTPUT_FILE="${TMP_DIR}/live_%00d.ts"
OUTPUT_PLAYLIST="${TMP_DIR}/live.m3u8"

ffmpeg \
  -i pipe:0 \
  -c:a copy \
  -c:v libx264 \
  -bufsize 1024k \
  -strict experimental \
  -map 0 \
  -s 320x240 \
  -f ssegment \
  -segment_list $OUTPUT_PLAYLIST -segment_time 10 \
  -flags +global_header \
  -segment_list_size 10 -segment_list_flags +live \
  -segment_list_type hls \
  $OUTPUT_FILE

Receiving video stream

In order to receive videos from a source, we can listen to UDP packets using a GenServer

defmodule Streaming.Incoming do
  use GenServer

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def init(:ok) do
    incoming_port = Application.get_env(:streaming, :incoming_port)

    {:ok, _socket} = :gen_udp.open(incoming_port, [:binary,
      {:active, true}, {:buffer, 1024}
      ])
  end

  def handle_info({:udp, _socket, _ip, _port, data}, state) do
    IO.inspect "---> Received #{byte_size(data)} bytes from #{_port}"

    # Write to a bucket
    Streaming.Bucket.add data
    
    # Or write to file
    Streaming.Encoder.write data

    {:noreply, state}
  end

  def handle_info({_, _socket}, state) do
    {:noreply, state}
  end

end

The Bucket module is the same as the key value store module on Elixir website. The Encoder is a module that writes to the FFMPEG script above. It is the part where the data should be written into multiple process for multiple resolutions.

This incoming module will store any stream into the same file with the same configuration, so you will need to change that too.

Start the process when the app or the stream starts

def init(_state) do
    # Open a port for the external process
    #   wait for stdin.
    port = Port.open({:spawn, "bin/read_hls"}, [:binary])

    {:ok, port}
  end

Then write to it from Streaming.Incoming

def encode(data) do
    GenServer.cast(:encoder, {:encode, data})
  end

def handle_cast({:encode, data}, port) do
    port |> Port.command(data)

    {:noreply, port}
  end

Streaming the live data
You can write the response from the FFMPEG process, back to the key value storage and stream it to client. Keep in mind that unlike streaming files, it will take a lot of memory. It is also FILO, so you will need to reverse the list.

Another option which I use in the example, is to stream the files that FFMPEG created. When a client request the live stream, serve the playlist.m3u8 file and it will contain a list of all the *.ts video files.

Testing the stream

Run this command in order to stream to the server.

    ffmpeg -y \
      -i $MEDIA_FILE \
      -c:a aac -ac 2 -ar 44100 \
      -c:v libx264 \
      -movflags frag_keyframe+empty_moov \
      -strict experimental \
      -crf 18 \
      -b:a 64k -b:v 64k -bufsize 64k \
      -pix_fmt yuv420p -profile:v baseline -level 1.3 -maxrate 192K -bufsize 1024k \
      -f mpegts -hls_time 9 -hls_list_size 0 \
      -s 1280x720 \
      -threads 12 \
      udp://127.0.0.1:3001

You should see the playlist under the ./tmp folder.

idi527

idi527

It seems like it wouldn’t be cheaper. With nginx-rtmp (which executes ffmpeg) I can stream all day and pay just the ec2 price, with elastic transcoder I would have to pay $0.015 * 60 * 24 = $21.6 additionally.

aphillipo

aphillipo

Also worth looking at this New York Times project that abstracts the transcoding service allowing you to change in the future and not be locked in:

It looks very very well written…

Where Next?

Popular in Wikis Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
Eiji
At start some definitions: HTTPS (is a protocol for secure communication over a computer network which is widely used on the Internet) -...
New
anildigital
Here is list of plugins for different editors Sublime Text 3 — Elixir.tmbundle - https://github.com/elixir-editors/elixir-tmbundle/#co...
New
georgeguimaraes
Hi people, since the new year is coming, I’d like to plan my travels for events in 2017. So, what events (Elixir or FP related) that you...
New
axelson
With the new year I am looking at travel for this year and I’d love to base some travel around Elixir. So in the spirit of the 2017 threa...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
swelham
Introduction After some discussion with a few other members we thought it might be an idea to start a thread where we can post about libr...
New
OvermindDL1
It was stated at https://github.com/elixir-lang/elixir/issues/6172 that Mirrors of the primary Elixir website would be useful since Cloud...
New
AstonJ
Wonder if we can compile a list of learning resources, blog posts, talks, threads etc to help those who are just learning about Contexts....
New
gmile
:httpc HTTP client is part of Erlang standard library, and as such can be easily used in Elixir code too. One particular advantage of usi...
New

Other popular topics Top

SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement