crtmpserver + ffmpeg

This post will show the process of installing , running and using crtmpserver on ubuntu 64 bit machine with gstreamer .

gcc and cmake

We shall build gstreamer directly from sources . For this we first need to determine if gcc is installed on the machine .

If not installed then  run the following command

GNU Compiler Collection (GCC) is a compiler system produced by the GNU Project supporting various programming languages( C, C++, Objective-C, Fortran, Java, Ada, Go etc).

sudo apt-get install build-essential

once it is isnatlled it can be tested with printing the version

Screenshot from 2016-06-09 11-24-33.png

cmake is a software compilation tool.It uses compiler independent configuration files, and generate native makefiles and workspaces that can be used in the differemt compiler environment .

Crtmpserver

To get the source code from git install git first . Then clone the project from https://github.com/j0sh/crtmpserver

sudo apt-get git
git clone https://github.com/j0sh/crtmpserver.git
cd crtmpserver/builders/cmake

Next we create all makefile’s using cmake .

cmake .

Output should look as follows

Screenshot from 2016-06-09 11-47-05

Run make to do compilation

make

Screenshot from 2016-06-09 11-57-19

Run using following command . If should print out a list of ports and their respecting functions

./crtmpserver/crtmpserver crtmpserver/crtmpserver.lua

+—————————————————————————–+
| Services|
+—+—————+—–+————————-+————————-+
| c | ip | port| protocol stack name | application name |
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 1112| inboundJsonCli| admin|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 1935| inboundRtmp| appselector|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 8081| inboundRtmps| appselector|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 8080| inboundRtmpt| appselector|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 6666| inboundLiveFlv| flvplayback|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 9999| inboundTcpTs| flvplayback|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 6665| inboundLiveFlv| proxypublish|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 8989| httpEchoProtocol| samplefactory|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 8988| echoProtocol| samplefactory|
+—+—————+—–+————————-+————————-+
|tcp| 0.0.0.0| 1111| inboundHttpXmlVariant| vptests|
+—+—————+—–+————————-+————————-+

If you the following types of errors while pushing a stream to crtmpserver , they just denote they your pipe is not using the correct format.

/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpacceptor.cpp:154 Client connected: 127.0.0.1:55524 -> 0.0.0.0:8080
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:119 Handlers count changed: 11->12 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/http/basehttpprotocol.cpp:281 Headers section too long
/home/altanai/crtmpserver/sources/thelib/src/protocols/http/basehttpprotocol.cpp:153 Unable to read response headers: CTCP(16) <-> TCP(13) <-> [IHTT(14)] <-> IH4R(15)
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpcarrier.cpp:89 Unable to signal data available
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:129 Handlers count changed: 12->11 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/protocolmanager.cpp:45 Enqueue for delete for protocol [IH4R(15)]
/home/altanai/crtmpserver/sources/thelib/src/application/baseclientapplication.cpp:240 Protocol [IH4R(15)] unregistered from application: appselector
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpacceptor.cpp:154 Client connected: 127.0.0.1:44964 -> 0.0.0.0:9999
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:119 Handlers count changed: 11->12 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/ts/inboundtsprotocol.cpp:211 I give up. I'm unable to detect the ts chunk size
/home/altanai/crtmpserver/sources/thelib/src/protocols/ts/inboundtsprotocol.cpp:136 Unable to determine chunk size
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpcarrier.cpp:89 Unable to signal data available
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:129 Handlers count changed: 12->11 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/protocolmanager.cpp:45 Enqueue for delete for protocol [ITS(17)]
/home/altanai/crtmpserver/sources/thelib/src/application/baseclientapplication.cpp:240 Protocol [ITS(17)] unregistered from application: flvplayback
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpacceptor.cpp:154 Client connected: 127.0.0.1:37754 -> 0.0.0.0:1935
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:119 Handlers count changed: 11->12 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/rtmp/inboundrtmpprotocol.cpp:77 Handshake type not implemented: 85
/home/altanai/crtmpserver/sources/thelib/src/protocols/rtmp/basertmpprotocol.cpp:309 Unable to perform handshake
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpcarrier.cpp:89 Unable to signal data available
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:129 Handlers count changed: 12->11 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/protocolmanager.cpp:45 Enqueue for delete for protocol [IR(19)]
/home/altanai/crtmpserver/sources/thelib/src/application/baseclientapplication.cpp:240 Protocol [IR(19)] unregistered from application: appselector
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpacceptor.cpp:154 Client connected: 127.0.0.1:48368 -> 0.0.0.0:6666
/home/altanai/crtmpserver/sources/thelib/src/protocols/liveflv/inboundliveflvprotocol.cpp:51 _waitForMetadata: 1
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:119 Handlers count changed: 11->12 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/liveflv/baseliveflvappprotocolhandler.cpp:45 protocol CTCP(16) <-> TCP(20) <-> [ILFL(21)] registered to app flvplayback
/home/altanai/crtmpserver/sources/thelib/src/protocols/liveflv/inboundliveflvprotocol.cpp:102 Frame too large: 6324058
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/tcpcarrier.cpp:89 Unable to signal data available
/home/altanai/crtmpserver/sources/thelib/src/netio/epoll/iohandlermanager.cpp:129 Handlers count changed: 12->11 IOHT_TCP_CARRIER
/home/altanai/crtmpserver/sources/thelib/src/protocols/protocolmanager.cpp:45 Enqueue for delete for protocol [ILFL(21)]
/home/altanai/crtmpserver/sources/thelib/src/protocols/liveflv/baseliveflvappprotocolhandler.cpp:58 protocol [ILFL(21)] unregistered from app flvplayback

ffmpeg

Download and install ffmpeg from git

 git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
cd ffmpeg

Once the source code is obtained we need to configure , make and make install it .
We need to have following plugins for muxing and ecoding like libx264 for h264parse , so we configure with the following options

./configure \
  --prefix="$HOME/ffmpeg_build" \
  --pkg-config-flags="--static" \
  --extra-cflags="-I$HOME/ffmpeg_build/include" \
  --extra-ldflags="-L$HOME/ffmpeg_build/lib" \
  --bindir="$HOME/bin" \
  --enable-gpl \
  --enable-libass \
  --enable-libfreetype \
  --enable-libopus \
  --enable-libtheora \
  --enable-libvorbis \
  --enable-libx264 \
  --enable-libx265 \
  --enable-nonfree

the make and make install

make
sudo make install

Screenshot from 2016-06-09 16-59-49

Incase of errors  on ffmpeg configure command , you need to install the respective missing / not found library

libass

sudo apt-get install libass-dev

lamemp3

sudo apt-get install libmp3lame-dev

libaacplus

sudo apt-get install autoconf
sudo apt-get install libtool

wget -O libaacplus-2.0.2.tar.gz http://tipok.org.ua/downloads/media/aacplus/libaacplus/libaacplus-2.0.2.tar.gz
tar -xzf libaacplus-2.0.2.tar.gz
cd libaacplus-2.0.2
./autogen.sh --with-parameter-expansion-string-replace-capable-shell=/bin/bash --host=arm-unknown-linux-gnueabi --enable-static

make
sudo make install

libvorbis
compressed audio format for mid to high quality (8kHz-48.0kHz, 16+ bit, polyphonic) audio and music at fixed and variable bitrates from 16 to 128 kbps/channe. It is from the same reank as MPEG4 AAC

wget http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.2.tar.bz2
tar -zxvf libvorbis-1.3.2.tar.bz2
cd libvorbis-1.3.2
./configure && make && make install

libx264
encoding video streams into the H.264/MPEG-4 AVC compression format, and is released under the terms of the GNU GPL.

git clone git://git.videolan.org/x264
cd x264
./configure --host=arm-unknown-linux-gnueabi --enable-static --disable-opencl
make
sudo make install

libvpx
libvpx is an emerging open video compression library which is gaining popularity for distributing high definition video content on the internet.

sudo apt-get install checkinstall
git clone https://chromium.googlesource.com/webm/libvpx
cd libvpx
./configure
make
sudo checkinstall --pkgname=libvpx --pkgversion="1:$(date +%Y%m%d%H%M)-git" --backup=no     --deldoc=yes --fstrans=no --default

librtmp
librtmp provides support for the RTMP content streaming protocol developed by Adobe and commonly used to distribute content to flash video players on the web.

sudo apt-get install libssl-dev
cd /home/pi/src
git clone git://git.ffmpeg.org/rtmpdump
cd rtmpdump
make SYS=posix
sudo checkinstall --pkgname=rtmpdump --pkgversion="2:$(date +%Y%m%d%H%M)-git" --backup=no --deldoc=yes --fstrans=no --default

Reference:
http://www.videolan.org/developers/x265.html
https://trac.ffmpeg.org/wiki/CompilationGuide/RaspberryPi
http://wiki.serviio.org/doku.php?id=howto:linux:install:raspbian
http://lame.sourceforge.net/

Additionally “pkg-config –list-all” command list down all the installed libraries.


RTMP streaming

1.start the stream from linux machine using ffmpeg

ffmpeg -f video4linux2 -s 320x240 -i /dev/video0 -f flv -s qvga -b 750000 -ar 11025 -metadata streamName=aaa "tcp://<hidden_ip>:6666/live";

Screenshot from 2016-06-11 17-50-02

2.view the incoming packets and stats on terminal at crtmpserver

Screenshot from 2016-06-11 17-53-22

3.playback the livestream from another machine

using ffplay
ffplay -i rtmp://server_ip:1935/live/ccc

Screenshot from 2016-06-09 15-43-58

RTSP streaming

1.start the rtsp stream from linux machine using ffmpeg

here using resolution 320×240 and stream name test

ffmpeg -f video4linux2 -s 320x240 -i /dev/video0 -an -r 10 -c:v libx264 -q 1 -f rtsp -metadata title=test rtsp://server_ip:5554/flvplayback

crtmp2

2.view the incoming packets and stats on terminal at crtmpserver

3.playback the livestream from another machine using

ffplay

ffplay rtsp://server_ip:5554/flvplayback/test

Screenshot from 2016-06-09 18-17-07

VLC

vlc rtsp://server_ip:5554/flvplayback/test

 

 

Wowza RTMP Authentication with Third party Token provider over Tiny Encryption Algorithm (TEA)

this article is focused on  Wowza RTMP Authentication with  Third party Token provider over Tiny Encryption Algorithm (TEA)  and  is a continuation of the previous post about setting up a basic RTMP Authentication module on Wowza Engine above version 4.

The task is divided into 3 parts .

  1. RTMP Encoder Application
  2. Wowza RTMP Auth module
  3. Third party Authentication Server

The component diagram is as follows :

Copy of Publisher App iOS

The detailed explanation of the components are :

1.Wowza RTMP Auth module

The Wowza Server receives a rtmp stream url in the format as :

rtmp://username:pass@wowzaip:1935/Application/stteamname

It considers the username and pass to be user credentials . RTMP auth Module invokes the getPassword() function inside of deployed application class  passing the username as parameter.  The username is then  encrypted using TEA ( Tiny Encryption algorithm)

TEA is a block cipher  which is based on symmetric ( private) key encryption . Input is a 64 bit of plain or cipher text with a 128 bit key resulting in output of cipher or plain text respectively.

The code for encryption  is


TEA.encrypt( username, sharedSecret );

The code to make a connection to third party auth server is


 url = new URL(serverTokenValidatorURL);
 
 URLConnection connection;
 connection = url.openConnection();
 connection.setDoOutput(true);

OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
 out.write("clientid=" + TEA.encrypt( username, sharedSecret ););
 out.close(); 

The sharedsecret is the common key which is with both the Auth server and wowza server . It must be atleast a 16 digit alphanumeric / special character based key . An example of shared secret is abcdefghijklmnop .The value can be stored as property in Application.xml file.

<Property>
<Name>secureTokenSharedSecret</Name>
<Value><![CDATA[abcdefghijklmnop]]></Value>
</Property>

<Property>
<Name>serverTokenValidatorURL</Name>
<Value>http://127.0.0.1:8080/TokenProvider/authentication/token</Value&gt;
</Property>

The values of serverTokenValidatorURL is the third party auth server listening for REST POST request .

The code for receiving the incoming  resulting json data is


	ObjectMapper mapper = new ObjectMapper();
	JsonNode node = mapper.readTree(connection.getInputStream()); 
	node = node.get("publisherToken") ;
	String token = node.asText();
        String token2 =TEA.decrypt(token, sharedSecret);

2.Third party Authentication Server

The 3rd party Auth server stores the passwords for users or performs oauth based authentication . It uses a shared secret key to decrypt the token based on TEA as explained in above section .

The code to decrypt the incoming clientId


TEA.decrypt(id, sharedSecret);

Add own custom logic to check files , databases etc for obtaining the password corresponding to the username as decrypted above.

The code to encrypt the password for the user if exists or send invalid response if non exists is


        try {

            String clientID = TEA.decrypt(id, sharedSecret);
            
            String token= findUserPassword(clientID);
            
             token = TEA.encrypt(token, sharedSecret); 
                        
            return "{\"publisherToken\":\""  + token+ "\"}";
            
        }catch (Exception ex) {

            return "{\"error\":\"Invalid Client\"}";
        }

The final callflow thus becomes :

Copy of Publisher App iOS (1)

Screenshots :

Screenshot_2015-09-16-20-22-37Screenshot_2015-09-17-18-36-23Screenshot_2015-09-16-20-22-42Screenshot_2015-09-16-20-23-30

Wowza Secure URL params Authentication for streams in an application

To secure the publishers for a common application through username -password specific for stream names , this post is useful . It  uses Module Core Security to prompt back the user for supplying credentials.

The detailed code to check the rtmp query-string for parameters  and performs the checks –  is user is allowed to connect and is user allowed to stream on given stream name is given below .

Initialize the hashmap containing publisher clients and IapplicationInstance

HashMap <Integer, String> publisherClients =null;
IApplicationInstance appInstance = null;

On app start initilaize the IapplicationInstance object .

public void onAppStart(IApplicationInstance appInstance)
{
    this.appInstance = appInstance;
}

Onconnect is called called when any publisher tries to connects with media server. At this event collect the username and clientId from the client.
Check if publisherclient contains the userName which client has provided else reject the connection .

public void onConnect(IClient client, RequestFunction function, AMFDataList params)
{

AMFDataObj obj = params.getObject(2);
AMFData data = obj.get("app");

if(data.toString().contains("?")){

   String[] paramlist = data.toString().split();
   String[] userParam = paramlist[1].split("=");
   String userName = userParam[1];

   if(this.publisherClients==null){
       this.publisherClients = new HashMap<Integer, String>();
}

if(this.publisherClients.get(client.getClientId())==null){
    this.publisherClients.put(client.getClientId(),userName);
} else {
    client.rejectConnection();
}
}
}

AMFDataItem: class for marshalling data between Wowza Pro server and Flash client.

As the event user starts to publish a stream after sucessful connection Onpublishing function is called . It extracts the stream name from the client ( function extractStreamName() )and checks if user is allowed to stream on the given streamname (function isStreamNotAllowed()) .

public void publish(IClient client, RequestFunction function, AMFDataList params)
{
String streamName = extractStreamName(client, function, params);
if (isStreamNotAllowed(client, streamName))
{
sendClientOnStatusError(client, NetStream.Publish.Denied, "Stream name not allowed for the logged in user: "+streamName);
client.rejectConnection();
}
else{
invokePrevious(client, function, params);
}

}

Function when publisher disconnects from server . It removes the client from publisherClients.

public void onDisconnect(IClient client)
{
if(this.publisherClients!=null){
this.publisherClients.remove(client.getClientId());
}
}

The function to extract a streamname is


public String extractStreamName(IClient client, RequestFunction function, AMFDataList params)
{
String streamName = params.getString(PARAM1);
if (streamName != null)
{
String streamExt = MediaStream.BASE_STREAM_EXT;

String[] streamDecode = ModuleUtils.decodeStreamExtension(streamName, streamExt);
streamName = streamDecode[0];
streamExt = streamDecode[1];
}

return streamName;
}

The fucntion to check if streamname is allowed for the given user


public boolean isStreamNotAllowed(IClient client, String streamName)
{
WMSProperties localWMSProperties = client.getAppInstance().getProperties();
String allowedStreamName = localWMSProperties.getPropertyStr(this.publisherClients.get(client.getClientId()));
String sName="";
if(streamName.contains("?"))
sName = streamName.substring(0, streamName.lastIndexOf(&amp;amp;quot;?&amp;amp;quot;));
else
sName = streamName;
return !sName.toLowerCase().equals(allowedStreamName.toLowerCase().toString()) ;
}

On adding the application to wowza server make sure that the ModuleCoreSecurity is present under Modules in Application.xml

<Module>
<Name>ModuleCoreSecurity</Name>
<Description>Core Security Module for Applications</Description>
<Class>com.wowza.wms.security.ModuleCoreSecurity</Class>
</Module>




Also ensure that property securityPublishRequirePassword is present under properties

<Property>
<Name>securityPublishRequirePassword</Name>
<Value>true</Value>
<Type>Boolean</Type>
</Property>

Add the user credentials as properties too. For example to give access to testuser with password 123456 to stream on myStream include the following ,

<Property>
<Name>testUser</Name>
<Value>myStream</Value>
<Type>String</Type>
</Property>

Also include the mapping of user and password inside of conf/publish.password file

# Publish password file (format [username][space][password])
# username password

testuser 123456