Signaling stack implementation

Yerken Tussupbekov
3 min readNov 25, 2020

No code was written prior to that and we simply discussed the motivation and requirements for our video streaming solution.

We will start by creating a signaling stack. The purpose of a signaling service is to process an intention of starting a video stream or joining an ongoing video stream. It is present whenever a real-time media exchange is required, e.g. Twitch, Zoom or VoIP-based services. This usually involves so-called negotiation stage, i.e. both parties announcing supported video formats and means of exchanging media (more to follow on that). It is important to understand that the signaling service is meant to be the entry point for establishing a media connection between a user and the media processing service (which we will work on later on).

“Precision” is the code name chosen for the signaling stack. The code is written in Rust and hosted here: https://github.com/ideahitme-github/precision

Disclaimer: at the time of writing this post I am completely new to Rust. So take everything written here with a grain of salt

API

I like to start the implementation with defining an API. Two of the most common ways to model an API are RPC and REST. You can read more about when to choose which here. But in short, REST is more appropriate for resource-based services. In our case, our API will be more action or verb-based and RPC is a more appropriate model. There are various way to “do” RPC and GRPC is by far the most popular one — I recommend reading more on what GRPC is and how it works: https://grpc.io/docs/what-is-grpc/core-concepts/

We will define the API by using protobuf, a technology developed by Google, which allows to define the service and its RPC methods using a predefined protobuf IDL. There are various ways to translate the protobuf-IDL definitions into the programming language of your choice (Rust in this case), but more on that later:

syntax = "proto3";package precision;message CreateStreamRequest {
string sdp_offer = 1;
string user_id = 2;
}
message CreateStreamResponse {}service Precision {
rpc CreateStream(CreateStreamRequest) returns (CreateStreamResponse) {}
}

TIP: always mark RPC methods as single argument with the name format following: <method_name>Request. Similarly use a predefined struct <method_name>Response for the response type

CreateStream is an API to be invoked by clients in order to start a stream. The request type encompasses two fields: `sdp_offer` and `user_id`. The latter will be needed to understand who is the streamer. The former however is needed as part of so-called SDP exchange.

SDP

Remember how I mentioned that the video streaming service requires an understanding of how to reach the user and what video formats are supported and similarly the user needs to be aware of the media streaming service as well as the video formats it supports ? In order to carry that information we will use an SDP — Session Description Protocol. More on that here

The name offer is used to note that the original request was initiated by the user. The reply will come as an SDP answer. And this will form a single round of SDP negotiation

To generate an SDP answer we will have to relay the offer to the media server. Therefore the actual processing of SDP is omitted here for now

Typically SDP is generated on the client/user side and in the next chapter we will see how that works on a iOS app example

Generating code

Now how do we translate our API into code ? We will use an open-source project called tonic: https://github.com/hyperium/tonic

The whole process of generating the required GRPC service and type definitions is done in the following few lines of code:

extern crate tonic_build;
fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::compile_protos("proto/server.proto")?; Ok(())}

This is the file: https://github.com/ideahitme-github/precision/blob/master/build.rs

As a result of running cargo build we have all the definition ready. So now we only need to implement the server. Implementation details can be found here: https://github.com/ideahitme-github/precision/blob/master/src/handler/handler.rs

Rust Learnings

Async programming:

Wrapper types:

Rust by example:

How to run and build Precision

You need to have a configured Rust environment with a reasonably new Rust version. The rest is super simple:

$ git clone https://github.com/ideahitme-github/precision.git 
$ cd precision
$ cargo run --bin precision

Very nice GUI based tester against a GRPC server: https://github.com/uw-labs/bloomrpc

Next

We going to implement a very basic iOS app to start a camera captured stream

--

--