Android Streaming Live Camera Video to Web Page

Long time back I am looking for a solution to stream android live camera video to a webpage over internet (not wifi). After doing some research I came up with a working solution by combining multiple components together. The tutorial helps you get started with real time video streaming.

If you are beginner, this article will introduce you to lot new things like real time streaming protocols (RTSP, RTMP), Wowza Media Engine, libstreaming, WAMP and jwplayer.

Below are the two protocols on which real time streaming really works. Go through the wikipedia links to get enough knowledge on underlaying technology behind real time streaming.

RTSP Protocol
Real Time Streaming Protocol is a networking protocol mainly used to stream real time media data like audio or video. It establishes a streaming session between client and server. In this tutorial we use this protocol while sending video stream from android mobile to streaming server.

Lean more about Real Time Streaming Protocol

RTMP Protocol
Real Time Messaging Protocol was developed by Adobe for Flash Player to transmit the realtime media (audio, video) between server and flash player. This protocol we use to receive video stream from server to flash player.

Lean more about Real Time Messaging Protocol

Below diagram is a high level architecture diagram of android video streaming. First android streams camera video to wowza media engine. Second wowza decodes the video and starts a streaming channel. Thirst webpage consumes wowza stream and plays the video on the page.

android-video-streaming-architecture

1. Downloading Libstreaming library

Building a RTSP library involves deep understanding of real time streaming protocols and good command over multiple java media APIs which is not easy for every beginner. Luckly Fyhertz made our lives easier by providing an excellent RTSP library called libstreaming for android. Using this library, streaming video / audio from android mobile can done with very few lines of code.

Download the library and keep it aside.

2. Installing & Configuring Wowza Media Engine

Wowza Media Engine is very popular streaming engine which can stream high quality video and audio. In our project it acts as server side streaming framework which receives video from android device and starts a streaming service which will be again consumed by webpage to display the video.

Wowza also comes with an admin panel called Wowza Media Engine Manager to control streaming channels, publishers and other stuff. Unfortunately wowza is not a free software, you will have to buy a commercial license. But don’t worry, it comes with a trail period of 6 months which is more than enough for testing.

2.1 Registration
In order to use Wowa media engine you need to register and get a license key first. The key will be sent to your email address after the registration. So make sure that you entered a valid email address instead of abc@abc.com :)

Once you complete registration here, you can find the license key in the email.

wowza registration license key android

2.2 Downloading & Installing
1. Download Wowza Media Engine from here
2. Run the installer and enter the license key when it asks for it.

2.3 Creating publisher username and password
Once wowza installed, open Wowza Streaming Engine Manager from Start => All Programs. This opens up an admin panel in the browser. While streaming from android mobile, the streaming video needs to be authenticated with Wowza before start decoding it. So we need to create a publisher username and password first. These credentials we will use in our android app later.

To create a publisher, click on Server ⇒ Publisher ⇒ Add Publisher and enter credentials.

wowza creating publisher android video streaming
wowza creating publisher android video streaming

2.4 Creating streaming channel
You can create your own streaming url in the admin panel. But Wowza already creates a channel(app) for you named live. I am going to use the same app in this tutorial. If you want, you can create your own.

Here we completes wowza setup. Now let’s start the android project.

3. Creating Android project

1. In Eclipse create a new project by navigating to File ⇒ New ⇒ Android Application Project and fill required details.

2. Now import the downloaded libstreaming project into your Eclipse workspace. File ⇒ Import ⇒ Android ⇒ Existing Android Code Into Workspace and browse to libstreaming.

3. Now add the libstreaming project as a library project to our project. Right click on our project ⇒ Properties. It will open up a dialog window. On the left select Android and on the Right under Libraryadd libstreaming project.

android libstreaming library

4. As we are accessing the camera, we need to add the permissions in AndroidManifest.xml first. AddINTERNET, WRITE_EXTERNAL_STORAGE, RECORD_AUDIO, CAMERA permissions in your mainifest file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.androidvideostreaming"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />
    <uses-feature
        android:name="android.hardware.camera.autofocus"
        android:required="false" />

    <supports-screens
        android:largeScreens="true"
        android:normalScreens="true"
        android:smallScreens="true"
        android:xlargeScreens="true" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.androidvideostreaming.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

5. Open the activity layout file main activity and add the following content (My layout file nameactivity_main.xml). Here we are adding libstreaming surfaceview for camera preview.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/surface_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:background="@android:color/black" >

    <net.majorkernelpanic.streaming.gl.SurfaceView
        android:id="@+id/surface"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

</FrameLayout>

6. Now I am creating a class to keep wowza configuration values like streaming url, publisher username and password. Create a class named AppConfig.java and add the following. Replace the streaming url (keep your PC ip address), username and password with your wowza details.

public class AppConfig {
	public static final String STREAM_URL = "rtsp://192.168.43.233:1935/live/android_test";
	public static final String PUBLISHER_USERNAME = "ravi";
	public static final String PUBLISHER_PASSWORD = "passtemp";
}

7. Open your main activity class and implement the class from RtspClient.Callback, Session.Callback,SurfaceHolder.Callback and initialize required variables and class instances.

public class MainActivity extends Activity implements RtspClient.Callback,
		Session.Callback, SurfaceHolder.Callback {
import net.majorkernelpanic.streaming.Session;
import net.majorkernelpanic.streaming.gl.SurfaceView;
import net.majorkernelpanic.streaming.rtsp.RtspClient;
import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity implements RtspClient.Callback,
		Session.Callback, SurfaceHolder.Callback {

	// log tag
	public final static String TAG = MainActivity.class.getSimpleName();

	// surfaceview
	private static SurfaceView mSurfaceView;

	// Rtsp session
	private Session mSession;
	private static RtspClient mClient;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		// getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.activity_main);

		mSurfaceView = (SurfaceView) findViewById(R.id.surface);

		mSurfaceView.getHolder().addCallback(this);

		// Initialize RTSP client
		initRtspClient();
	}

	@Override
	protected void onResume() {
		super.onResume();
		
		toggleStreaming();
	}
	
	@Override
	protected void onPause(){
		super.onPause();
		
		toggleStreaming();
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder arg0) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {

	}

	@Override
	public void onBitrareUpdate(long bitrate) {

	}

	@Override
	public void onSessionError(int reason, int streamType, Exception e) {

	}

	@Override
	public void onPreviewStarted() {

	}

	@Override
	public void onSessionConfigured() {

	}

	@Override
	public void onSessionStarted() {

	}

	@Override
	public void onSessionStopped() {

	}

	@Override
	public void onRtspUpdate(int message, Exception exception) {

	}
}

8. initRtspClient() will initialize Rtsp client which takes care of streaming video to wowza media server. Add the following code in your main activity. toggleStreaming() takes care of toggling video stream on/off. Finally in OnDestroy() method we have to release rtsp client, session and surfaceview.

private void initRtspClient() {
		// Configures the SessionBuilder
		mSession = SessionBuilder.getInstance()
				.setContext(getApplicationContext())
				.setAudioEncoder(SessionBuilder.AUDIO_NONE)
				.setAudioQuality(new AudioQuality(8000, 16000))
				.setVideoEncoder(SessionBuilder.VIDEO_H264)
				.setSurfaceView(mSurfaceView).setPreviewOrientation(0)
				.setCallback(this).build();

		// Configures the RTSP client
		mClient = new RtspClient();
		mClient.setSession(mSession);
		mClient.setCallback(this);
		mSurfaceView.setAspectRatioMode(SurfaceView.ASPECT_RATIO_PREVIEW);
		String ip, port, path;

		// We parse the URI written in the Editext
		Pattern uri = Pattern.compile("rtsp://(.+):(\\d+)/(.+)");
		Matcher m = uri.matcher(AppConfig.STREAM_URL);
		m.find();
		ip = m.group(1);
		port = m.group(2);
		path = m.group(3);

		mClient.setCredentials(AppConfig.PUBLISHER_USERNAME,
				AppConfig.PUBLISHER_PASSWORD);
		mClient.setServerAddress(ip, Integer.parseInt(port));
		mClient.setStreamPath("/" + path);
	}

	private void toggleStreaming() {
		if (!mClient.isStreaming()) {
			// Start camera preview
			mSession.startPreview();

			// Start video stream
			mClient.startStream();
		} else {
			// already streaming, stop streaming
			// stop camera preview
			mSession.stopPreview();

			// stop streaming
			mClient.stopStream();
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		mClient.release();
		mSession.release();
		mSurfaceView.getHolder().removeCallback(this);
	}

Complete Code
Following is the full code of my main activity.

package info.androidhive.androidvideostreaming;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.majorkernelpanic.streaming.Session;
import net.majorkernelpanic.streaming.SessionBuilder;
import net.majorkernelpanic.streaming.audio.AudioQuality;
import net.majorkernelpanic.streaming.gl.SurfaceView;
import net.majorkernelpanic.streaming.rtsp.RtspClient;
import net.majorkernelpanic.streaming.video.VideoQuality;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity implements RtspClient.Callback,
		Session.Callback, SurfaceHolder.Callback {
	// log tag
	public final static String TAG = MainActivity.class.getSimpleName();

	// surfaceview
	private static SurfaceView mSurfaceView;

	// Rtsp session
	private Session mSession;
	private static RtspClient mClient;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		// getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.activity_main);

		mSurfaceView = (SurfaceView) findViewById(R.id.surface);

		mSurfaceView.getHolder().addCallback(this);

		// Initialize RTSP client
		initRtspClient();		

	}

	@Override
	protected void onResume() {
		super.onResume();
		
		toggleStreaming();
	}
	
	@Override
	protected void onPause(){
		super.onPause();
		
		toggleStreaming();
	}

	private void initRtspClient() {
		// Configures the SessionBuilder
		mSession = SessionBuilder.getInstance()
				.setContext(getApplicationContext())
				.setAudioEncoder(SessionBuilder.AUDIO_NONE)
				.setAudioQuality(new AudioQuality(8000, 16000))				
				.setVideoEncoder(SessionBuilder.VIDEO_H264)
				.setSurfaceView(mSurfaceView).setPreviewOrientation(0)
				.setCallback(this).build();

		// Configures the RTSP client
		mClient = new RtspClient();
		mClient.setSession(mSession);
		mClient.setCallback(this);
		mSurfaceView.setAspectRatioMode(SurfaceView.ASPECT_RATIO_PREVIEW);
		String ip, port, path;

		// We parse the URI written in the Editext
		Pattern uri = Pattern.compile("rtsp://(.+):(\\d+)/(.+)");
		Matcher m = uri.matcher(AppConfig.STREAM_URL);
		m.find();
		ip = m.group(1);
		port = m.group(2);
		path = m.group(3);

		mClient.setCredentials(AppConfig.PUBLISHER_USERNAME,
				AppConfig.PUBLISHER_PASSWORD);
		mClient.setServerAddress(ip, Integer.parseInt(port));
		mClient.setStreamPath("/" + path);
	}

	private void toggleStreaming() {
		if (!mClient.isStreaming()) {
			// Start camera preview
			mSession.startPreview();

			// Start video stream
			mClient.startStream();
		} else {
			// already streaming, stop streaming
			// stop camera preview
			mSession.stopPreview();

			// stop streaming
			mClient.stopStream();
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		mClient.release();
		mSession.release();
		mSurfaceView.getHolder().removeCallback(this);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onSessionError(int reason, int streamType, Exception e) {
		switch (reason) {
		case Session.ERROR_CAMERA_ALREADY_IN_USE:
			break;
		case Session.ERROR_CAMERA_HAS_NO_FLASH:
			break;
		case Session.ERROR_INVALID_SURFACE:
			break;
		case Session.ERROR_STORAGE_NOT_READY:
			break;
		case Session.ERROR_CONFIGURATION_NOT_SUPPORTED:
			break;
		case Session.ERROR_OTHER:
			break;
		}

		if (e != null) {
			alertError(e.getMessage());
			e.printStackTrace();
		}
	}

	private void alertError(final String msg) {
		final String error = (msg == null) ? "Unknown error: " : msg;
		AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
		builder.setMessage(error).setPositiveButton("Ok",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
					}
				});
		AlertDialog dialog = builder.create();
		dialog.show();
	}

	@Override
	public void onRtspUpdate(int message, Exception exception) {
		switch (message) {
		case RtspClient.ERROR_CONNECTION_FAILED:
		case RtspClient.ERROR_WRONG_CREDENTIALS:
			alertError(exception.getMessage());
			exception.printStackTrace();
			break;
		}
	}

	@Override
	public void onPreviewStarted() {
	}

	@Override
	public void onSessionConfigured() {
	}

	@Override
	public void onSessionStarted() {
	}

	@Override
	public void onSessionStopped() {
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
	}

	@Override
	public void onBitrareUpdate(long bitrate) {
	}

}

Now we are done with our android application. Let’s create a web application which displays the streaming video on web page.

4. Creating web app

4.1 Installing WAMP:
I have already explained lot of times what is WAMP and it’s use (here & here). Download and Install WAMP from http://www.wampserver.com/en/ and start the program from Start => All Programs. Once started, you should be able to access http://localhost/ in the browser.

4.2 jWPlayer
On the web page to play streamed I am using jwplayer which supports RTMP protocol. jwplayer is a flash player with javascript API enabled which means you can control the player with javascript methods.

Create an account at http://www.jwplayer.com/sign-up/ and download the jWplayer. Once you login into jwplayer.com, you can download a copy from https://account.jwplayer.com/#/account. Click onDownload Self-Hosted Player at the bottom of the page.

1. Now create a folder in your wamp/www directory (Normally wamp will be located at C:/wamp). I am creating a folder named wowza_web_app inside www.

2. Inside wowza_web_app create two more folders named css and js.

3. Paste jwplayer files inside js folder. You have to copy jwplayer.js, jwplayer.html5.js andjwplayer.flash.swf. Also paste jquery-1.9.1.min. Download jquery from here

4. Create an html file named index.html in wowza_web_app and paste the following content. This page will have jwplayer and other UI to play and stop streaming video.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Android Video Streaming</title>
        <script src="js/jquery-1.9.1.min.js" type="text/javascript"></script>
        <script type="text/javascript" src="js/jwplayer.js"></script>       
        <script src="js/player.js" type="text/javascript"></script>
        <script>jwplayer.key = ""</script>
        <link rel="stylesheet" type="text/css" href="css/style.css" />       
    </head>

    <body>
        <!-- Header -->
        <div id="header">
            <div class="container">
                <h1>Android Video Streaming</h1>
            </div>
        </div>
        <!-- ./Header -->

        <!-- Video Preview -->
        <div class="container">            
            <div id="video_preview">                    
                <div id="player"></div><div class="clear"></div>
                <br/><br/><br/>
                <input type="text" id="stream_url" value="rtmp://192.168.43.233:1935/live/android_test"/><br/>
                <input type="button" id="btn_start" class="" value="Start" />
                <input type="button" id="btn_stop" class="" value="Stop"/>
            </div>
            <div class="clear"></div>
        </div>
        <!-- ./Video Preview -->

        <div class="container">
            <div class="info"><p>
                    Tutorial: <a href="#">http://www.androidhive.info/?p=4449&preview=true</a><br/>
                    <a href="http://www.wowza.com/">Wowza Media Engine</a><br/>
                    Thanks <a href="Fyhertz">@Fyhertz</a> for <a href="https://github.com/fyhertz/libstreaming">libstreaming</a><br/>
                    www.androidhive.info
                </p>
            </div>
        </div>
    </body>
</html>

5. Inside css folder create a file named style.css and paste the following styles.

/* 
    Document   : style
    Created on : May 31, 2014, 2:09:28 AM
    Author     : Ravi Tamada    
*/

body { 
    padding:0;
    margin: 0;
}
.clear{
    clear: both;
}
.container{
    width: 1100px;
    margin: 0 auto;
    padding: 0;
}
#header{
    text-align: left; 
    box-shadow: 0px 3px 3px #e3e3e3;
}
#header h1{
    font:normal 35px arial;
    color: #ed4365;
    margin: 0;
    padding: 15px 0;
}
#video_preview{
    text-align: center;
}
#player, #player_wrapper{
    margin: 0 auto !important;
    margin-bottom: 20px !important;
    margin-top: 60px !important;    
}
input#stream_url{
    background: none;
    border: 2px solid #92d07f;
    outline: none;
    padding: 5px 10px;
    font: 18px arial;
    color: #666;
    width: 600px;
    text-align: center;
}
#btn_start, #btn_stop{
    padding: 8px 30px;
    color: #fff;
    border: none;
    outline: none;
    font: normal 16px arial;
    border-radius: 6px;
    cursor: pointer;
    margin-top: 15px;
}
#btn_start{
    background: #3bbe13;    
}
#btn_stop{
    background: #e6304f;   
}
.info{
    margin-top: 80px;
    text-align: center;    
    font:normal 13px verdana;
}
.info p{
    line-height: 25px;
}
.info a{
    color: #f05539;
}

6. Create player.js file inside js folder and paste following javascript. This script takes care of initializing jwplayer and other button click events.

var data = [];
var jw_width = 640, jw_height = 360;

// Outputs some logs about jwplayer
function print(t, obj) {
    for (var a in obj) {
        if (typeof obj[a] === "object")
            print(t + '.' + a, obj[a]);
        else
            data[t + '.' + a] = obj[a];
    }
}

$(document).ready(function() {

    jwplayer('player').setup({
        wmode: 'transparent',
        width: jw_width,
        height: jw_height,
        stretching: 'exactfit'
    });

    $('#btn_start').click(function() {
        startPlayer($('#stream_url').val());
    });

    $('#btn_stop').click(function() {
        jwplayer('player').stop();
    });



    startPlayer($('#stream_url').val());
});

// Starts the flash player
function startPlayer(stream) {

    jwplayer('player').setup({
        height: jw_height,
        width: jw_width,
        stretching: 'exactfit',
        sources: [{
                file: stream
            }],
        rtmp: {
            bufferlength: 3
        }
    });

    jwplayer("player").onMeta(function(event) {
        var info = "";
        for (var key in data) {
            info += key + " = " + data[key] + "<BR>";
        }
        print("event", event);
    });

    jwplayer('player').play();
}

7. Now if you access http://localhost/wowza_web_app/index.html in browser, the page should render like below.

android video streaming using wowza media engine

Testing the app (localhost)

Start Wowza Enginer Mangaer from Start => All Programs. Once started you should able to accesshttp://localhost:8088/enginemanager/ in PC browser.

Make sure that the mobile and the PC are in the same wifi network.

Use Android Terminal Emulator app to check communication between mobile and PC. If you execute “ping pc_ip_address“, you should receive some data.

Try accessing http://your_pc_ip_address:8088/enginemanager/ in mobile browser. You should see wowza engine manager home page.

Once you are sure that the PC and mobile are in the same network, open both the apps (android & web) and click on Start in web page. You should able to see camera video streamed to webpage.

Exploring the libstreaming

The android app that I have created in this tutorial is very simple which lacks of lot options like start/stop video stream, changing video quality, using camera flash, using front and rare cameras. My intention was to make it as much as simple, so that every beginner can understands it. But the example given by libstreaming covers all these advanced options.

Download the Example3 and do explore yourself.

Hosting the wowza server (going live)

If you are serious about going the app to be public where anybody can stream their camera video over intenet, you need to host the Wowza engine on a server. The following links will gives you a hosting solution to host Wowza Media Engine (personally I haven’t tried though).

Google Cloud Platform Hosting
http://googlecloudplatform.blogspot.in/2013/12/you-can-now-deliver-any-screen.html

Wowza Featured Service Providers
http://www.wowza.com/customers

anyShare分享到: