webmcast

command module
v0.0.0-...-3571a2c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 16, 2017 License: MIT Imports: 27 Imported by: 0

README

webmcast

An experimental video streaming service.

The Idea

A generic WebM file looks like this:

EBML.

By setting the Segment's length to one of 7 reserved values (all of which mean "indeterminate"), it's possible to produce an infinite stream.

There is only one reserved word for Element Size encoding, which is an Element Size encoded to all 1's. Such a coding indicates that the size of the Element is unknown, which is a special case that we believe will be useful for live streaming purposes.

Infinite EBML.

Let's say a client connects at some point.

Barely in time for the best part.

Naturally, we have to send the EBML header and track descriptions first. We can't just start forwarding frames yet, though. Each WebM frame may depend on three previously-seen frames: the last keyframe, the previous frame, and an Alternate Reference frame, none of which the client has yet, causing a decoding error if one is needed. The solution is to only start from the next keyframe, which, by definition, references nothing; sending it is always OK. Further frames cannot reference any frame that came before the keyframe, so we can proceed as normal.

Oops, sorry, it was dropped.

Additionally, a WebM file (even an infinite one) can contain multiple segments. These segments will be played one after another if they contain the same tracks, so we can spawn a copy of the original stream with a different bitrate, then switch the client over by starting a new segment if a slow connection is detected. Kind of like adaptive streaming, see?

It's not the size of a cluster, it's the contents.

Sounds simple, huh? So simple, in fact, someone probably already thought to do that. That's right! We're live-streaming Matroska!

The Implementation

This code!

go build -i
./webmcast
How To Broadcast Stuff

PUT/POST a WebM to /stream/<name>. (Note that you have to register first, to obtain said name and a token.)

server=http://localhost:8000
name=test

ffmpeg $source \
    -c:v vp8 -b:v 2000k -keyint_min 60 -g 60 -deadline realtime -speed 6 \
    -c:a opus -b:a 64k \
    -f webm $server/stream/$name?$token

Or with gstreamer:

gst-launch webmmux name=mux streamable=true ! souphttpclientsink location=$server/stream/$name?$token \
           $video_source ! videoconvert ! vp8enc keyframe-max-dist=60 deadline=1 ! queue ! mux.video_0 \
           $audio_source ! vorbisenc ! queue ! mux.audio_0

Tips:

  • -g (or keyframe-max-dist) controls the spacing between keyframes. Keep it low (~2 seconds) to allow the stream to start faster for new viewers. ffmpeg tip: use -force_key_frames 00:02 instead if you don't know the framerate.

  • The stream may be split arbitrarily into many requests. For example, gstreamer sends each frame as a separate PUT by default.

  • The stream is kept alive for some time after a payload-carrying request ends. Thus, should the connection fail, it is possible to reconnect and continue streaming as if nothing happened. (Duplicate WebM headers are ignored.)

  • Multiple WebM streams can be concatenated (or sent as multiple requests to the same stream), as long as they contain the same tracks and use the same codecs. For example, you can switch bitrate mid-stream by restarting ffmpeg.

  • Sending frames faster than they are played back is OK. However, frames may or may not get dropped if buffers overflow, and clients that do not connect at the same time are likely to be severely desynchronized (and confused). ffmpeg tip: -re caps output speed at one frame per frame. gstreamer does that by default.

How To View Stuff

Visit /<name> in a web browser. There's a chat and everything. Alternatively, open /stream/<name> in a browser or a video player; a raw WebM will play.

The Reality (alt. name: "Known Issues")

As always, what looks good on paper doesn't always work in practice.

  • There are 7 ways to encode an "indeterminate" length. Naturally, the one that ffmpeg happens to use makes Chrome (48.0.2564.109/CrOS) crash. (The server will automatically recode it as one of the acceptable variants.)

  • When streaming from a webcam (not a random downloaded file for some reason) in VP9, Chrome crashes upon receiving the first frame (even when simply opening a file recorded with ffmpeg), Firefox loses most of the color (and stutters; however, this is likely because encoding & decoding VP9 is too CPU-intensive for my computer to handle), and VLC complains about a missing reference frame. Curiously, curl | ffmpeg accepts the stream just fine. All four use the same library (libvpx) for decoding, so...WTF?

  • VP8 is OK, though.

  • Of course, this thing is incompatible with static CDNs. For redistibution, additional instances must be run on separate servers and connected to form a directed tree.

Looks like all those overcomplicated standards like HLS or DASH exist for a reason, huh?

Documentation

Overview

POST /stream/<name> or PUT /stream/<name>

Broadcast a WebM video/audio file.

Accepted input: valid WebM split into arbitrarily many requests in absolutely
any way. Multiple files can be concatenated into a single stream as long as they
contain exactly the same tracks (i.e. their number, codecs, and dimensions.
Otherwise any connected decoders will error and have to restart. Changing,
for example, bitrate or tags is fine.)

GET /stream/<name>

Receive a published WebM stream. Note that the server makes no attempt
at buffering; if the stream is being broadcast faster than its native framerate,
the client will have to buffer and/or drop frames.

GET /stream/<name> [Upgrade: websocket]

Connect to a JSON-RPC v2.0 node.

Methods of `Chat`:

   * `SetName(string)`: assign a (unique) name to this client. This is required to...
   * `SendMessage(string)`: broadcast a simple text message to all viewers.
   * `RequestHistory()`: ask the server to emit notifications containing the last
     few broadcasted text messages.

TODO Methods of `Stream`.

Notifications:

   * `Chat.AcquiredName(user string)`: upon a successful `SetName`.
     May be emitted automatically at the start of a connection if already logged in.
   * `Chat.Message(user string, text string)`: a broadcasted text message.

GET /<name>

Open a simple HTML5-based player with a stream-local chat.

GET /rec/<name>

View a list of previously recorded streams.

GET /rec/<name>/<id>

Watch a particular recording in the HTML5 player.

GET /user/ POST /user/

>> password-old string, username, displayname, email, password, about optional[string]

POST /user/new

>> username, password, email string

POST /user/login

>> username, password string

POST /user/restore

>> username string OR email string

POST /user/restore?uid=int64&token=string

>> password string

GET /user/logout

POST /user/new-token

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL