Skip to main content

Streaming Media

Resolution, Aspect Ratio and Orientation

Resolution

Resolution means the native resolution of the camera (input). In the most situations this will be the same for the output. To set the resolution there is a function in the VideoSettings object called setResolution(Resolution res). If you set a resolution that the device doesn't support, nanoStream will automatically switch to the nearest resolution available on the device. A list of supported resolutions for the current video source can be obtained from getCapabilities().listAvailableVideoResolutions() on the nanoStream object.

Aspect Ratio

Aspect ratio means the aspect ratio of the outgoing stream. The aspect ratio determines if the input video needs to be cropped. The aspect ratio can be set through the setAspectRatio(AspectRatio aspectRatio) function on the VideoSettings object.

Supported Aspect Ratios

Aspect RatioAspectRatio value
Keep InputAspectRatio.RATIO_KEEP_INPUT
1:1AspectRatio.RATIO_1_1
4:3AspectRatio.RATIO_4_3
16:9AspectRatio.RATIO_16_9
3:4AspectRatio.RATIO_3_4
9:16AspectRatio.RATIO_9_16

Orientation

The default stream orientation is landscape. If you switch to portrait the resolution will swap width and height, e.g. from 640x480 to 480x640. You can set the stream orientation on the nanoStream object with the setStreamRotation function. The stream orientation needs to be set before starting the stream, it is not possible to switch the orientation during the stream.

Supported Orientations

OrientationRotation Value
LandscapeRotation.ROTATION_0
PortraitRotation.ROTATION_90
Landscape Upside DownRotation.ROTATION_180
Portrait Upside DownRotation.ROTATION_270

Example Combinations of Aspect Ratios and Orientations

The input resolution is set to 640x480 here. The red rectangle marks up the active area that is included in the output stream.

OrientationAspect RatioStream Area
Portrait1Keep InputScreenshot
Portrait14:3Screenshot
Portrait13:4Screenshot
Portrait116:9Screenshot
Portrait19:16Screenshot
LandscapeKeep InputScreenshot
Landscape4:3Screenshot
Landscape3:4Screenshot
Landscape16:9Screenshot
Landscape9:16Screenshot
1: In this sample APP we crop the preview so it doesn't look ugly, so the stream is actually larger then the preview.

Example

If you want to stream with a resolution of 640x360 but your device doesn't supports this resolution, you need to crop the resolution from 640x480 (this resolution is supported by the most devices) to 640x360. This can be done through the aspect ratio, so you need to set the aspect ratio to 16:9 to stream with a resolution of 640x360.

Implementation Example

public class MainActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nanoStreamSettings nss = new nanoStreamSettings();
VideoSettings vs = new VideoSettings();
...
vs.setResolution(new Resolution(640, 480)); // default value
vs.setAspectRatio(AspectRatio.RATIO_16_9); // default value is AspectRatio.KEEP_INPUT
...
streamLib = new nanoStream(nss);
streaLib.init();

streamLib.setStreamRotation(Rotation.ROTATION_0); // default value
...
}
...
}

Stream Type

The SDK supports differnet streaming modes:

  • Video and Audio
  • Video only
  • Audio only

You can en/disable Video/Audio in the nanoStreamSettings.object

Implementation Example

nanoStreamSettings nss = new nanoStreamSettings();
nss.setHaveVideo(true); // false
nss.setHaveAudio(true); // false

Server Authentication

In case authentication is required, the credentials can be set on the nanoStreamSettings object.

Implementation Example

nanoStreamSettings nss = new nanoStreamSettings();
nss.setAuthUser("user");
nss.setAuthPassword("password");

Local Recording

Description

The nanoStream Android SDK supports local file recording on the device in MP4 format. This document describes how to enable and configure nanoStream for local recording.

Steps to configure MP4 recording

MP4 recording can be configured with two function calls on a nanoStreamSettings object.

  • Enabling MP4 recording: setRecordMp4(boolean)
  • Setting up the file path: setMp4Path(String)

setRecordMp4(boolean)

The setRecordMp4 function takes a boolean as parameter to enable/disable the recording function.

setMp4Path(String)

The setMp4Path function takes a String as parameter. This string needs to be a valid file path (e.g. /sdcard/test.mp4). It is recommended to use the getExternalStorageDirectory or getExternalStoragePublicDirectory functions from the [Android Enviroment][8928e181] API, and add a file name to the returned path. Please find the code snippet below as an example.

Android Permission

To be able to write to an external file path your Android app needs the following permissions to be added to the app manifest (AndroidMainfest.xml).

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.STORAGE" />

Android 6.0

Due to the new permission handling in Android 6 (M) writing to external directories (DCIM) requires a permission by user. Writing to the applications own data directory (/Android/data/com.companyname.appname/) is not restricted.

Implementation Example

File externalFilePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File filePath = new File(externalFilePath, "myMp4File.mp4");
String mp4FilePath = filePath.getAbsolutePath();

nanoStreamSettings nss = new nanoStreamSettings();
nss.setRecordMp4(true);
nss.setMp4Path(mp4FilePath);

Adaptive Bitrate Streaming

By using the Adaptive Bitrate Control (ABC) the stream will automatically adjust to changes of the bandwidth. There are four modes available:

  • DISABLED: The Adaptive Bitrate Control is disabled.
  • QUALITY_DEGRADE: The video quality will be changed if the bandwidth changes. For instance, if not enough bandwidth is available, the video bitrate will be decreased, which in turn degrades the video quality.
  • FRAME_DROP: Low bandwidth is compensated by decreasing the framerate (FPS), but maintaining the video qualtiy.
  • QUALITY_DEGRADE_AND_FRAME_DROP: The video quality and the framerate (FPS) decreased if the not enough bandwidth is available.

Make sure to set the ABC settings before a stream is started.

Implementation Example

private AdaptiveBitrateControlSettings.AdaptiveBitrateControlMode abcMode = AdaptiveBitrateControlSettings.AdaptiveBitrateControlMode.QUALITY_DEGRADE_AND_FRAME_DROP;
private int videoBitrate = 500000;

private void initStreamLib() {
AdaptiveBitrateControlSettings abcSettings = new AdaptiveBitrateControlSettings(abcMode);
abcSettings.SetMaximumBitrate((int)(videoBitrate * 1.5));

nanoStreamSettings nss = new nanoStreamSettings();
nss.setAbcSettings(abcSettings);
}

Measuring the available bandwidth

For measuring the available bandwidth you can use the method runBandwidthCheck. After the check finished, the result can be used to set the bitrate for the nanoStream object. The check measures the bandwidth by running a test stream to the server. The BandwidthCheck Class has three public functions:

  • runBandwidthCheck(BandwidthCheckSettings settings, BandwidthCheckResultCallback callback())
  • forceStop()
  • abort()

There is a BandwidthCheckSettings Class, the constructor creates a standard object of BandwidthCheckSettings, with the following settings:

propertydefault valuesmeaning
prerollSeconds1 (in sec.)this is the pre roll time to connect to the server
runTime5 (in sec.)the run time of the bandwidth check
maxBitrate3000000 (bit/s = 3 MBit/s)the maximum bit rate for the bandwidth check
rtmpUrlemptythe rtmp url for the bandwidth check
streamIdemptythe stream id for the bandwidth check

With this settings you can call the runBandwidthCheck methode, the second parameter is the callback for the results. This callback class has a finished method that will be called after bandwidth check is done. The finished method has one parameter from type BandwidthCheckResult, this object has 6 getter methods:

  • getAverageBitrate() // the average measured bandwidth
  • getMedianBitrate() // the median measured bandwidth
  • getMaxBitrate() // the maximum measured bandwidth
  • getMinBitrate() // the minimum measured bandwidth
  • getRunTimeMS() // the run time in ms
  • getErrorCode() // the error code if all is ok this is nanoResults.N_OK (all error codes can be found in the nanoStream API Reference documentation for nanoResults)

The forceStop call stops the bandwidth check and will return the results that where measured until then. The abort call stops the bandwidth check but don't return any results.

The bandwidth check, sends a special type of metadata that will not be recorded on the Streaming Server.

Implementation Example

private BandwidthCheck bwCheck = null;

private class CustomBandwidthCheckResultCallback implements BandwidthCheckResultCallback {
@Override
public void finished(final BandwidthCheckResult bandwidthCheckResult) {
Log.d(TAG, "BandwidthCheck results: " +
"\n\tAverage Bitrate (kBit/s): " + bandwidthCheckResult.getAverageBitrate() / 1000 +
"\n\tMedian Bitrate (kBit/s): " + bandwidthCheckResult.getMedianBitrate() / 1000 +
"\n\tMax Bitrate (kBit/s): " + bandwidthCheckResult.getMaxBitrate() / 1000 +
"\n\tMin Bitrate (kBit/s): " + bandwidthCheckResult.getMinBitrate() / 1000 +
"\n\tRun Time (ms) : " + bandwidthCheckResult.getRunTimeMS());
}
}

private void initBandwidthCheck() {
if(null == bwCheck) {
BandwidthCheckSettings settings = new BandwidthCheckSettings();
settings.setRtmpUrl(serverUrl);
settings.setStreamId(streamName);
bwCheck = new BandwidthCheck();
bwCheck.runBandwidthCheck(settings, new CustomBandwidthCheckResultCallback());
}
}

##Snapshot

To get a snapshot (image) of the current preview/stream, the method takeSnapshot can be used. This is a non blocking function, for the result you need to implement the SnapshotCallback interface. The snapshot returns as a base64 encoded JPEG

Implementation Example

private class CustomSnapshotCallback implements SnapshotCallback {
@Override
void onSuccess(String arg0){
// do something with the base64 encoded JPEG.
}

@Override
void onFailure(){
Log.d(TAG, "takeSnapshot() failed!")
}
}
private void shapshot() {
streamLib.takeSnapshot(new CustomSnapshotCallback());
}

Camera Zoom

Description

The nanoStream Android SDK supports camera zoom, if the internal camera supports it. Therefor there are a few functions, the most important are:

FunctionReturn Typereturns
hasZoom()booleantrue if zoom is supported by the video source/ device
getZoomRatios()List<Integer>list with of ale zoom ratios
getZoom()intthe index of the List<Integer> that returned from getZoomRatios()
setZoom(int)intthe new index of the List<Integer> that returned from getZoomRatios()

It is recommended to use pinch to zoom, therefor you need to implement a ScaleGestureDetector.SimpleOnScaleGestureListener, and a pinch2zoom function, that takes the scalefactor from the SimpleOnScaleGestureListener as a int parameter, take a look at the [Implementation Example][ef1c8421].

getZoomRatios()

getZoomRatios() returns a List of Integer values, this values are the zoom ratios in 1/100 increments (e.g. a zoom of 3.2x is returned as 320).

setZoom(int)

The int parameter from setZoom(int zoom) is the index of zoom ratios that returns getZoomRatios().

Zoom Behavior on Camera Switch

During a camera switch (e.g. from back to front) the zoom remains unaffected.

Implementation Example

public class MainActivity extends Activity {
private ScaleGestureDetector scaleGestureDetector;
private List<Integer> mZoomRatios = null;

private nanoStream streamLib = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

nanoStreamSettings nss = new nanoStreamSettings();
// configure nanoStreamSettings

streamLib = new nanoStream(nss);

if(streamLib.hasZoom()) {
mZoomRatios = streamLib.getZoomRatio();
}

if(null == scaleGestureDetector) {
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureListener());
}
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
if (scaleGestureDetector != null)
{
scaleGestureDetector.onTouchEvent(event);
}
return true;
}

private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
if(null != streamLib) {
if (streamLib.hasZoom()) {
pinch2Zoom(detector.getScaleFactor());
}
}
return true;
}
}

public void pinch2Zoom(float scaleFactor) {
if (streamLib.hasZoom() && null != mZoomRatios) {
int zoomFactor = streamLib.getZoom();
float zoomRatio = mZoomRatios.get(zoomFactor) / 100f;
zoomRatio *= scaleFactor;
if (zoomRatio > 1.0f) {
if (scaleFactor > 1.0f) {
for (int i = zoomFactor; i < mZoomRatios.size(); i++) {
Double zoom = mZoomRatios.get(i) / 100.0;
if (zoom >= zoomRatio) {
streamLib.setZoom(i);
break;
}
}
} else {
for (int i = zoomFactor; i > 0; i--) {
Double zoom = mZoomRatios.get(i) / 100.0;
if (zoom <= zoomRatio) {
streamLib.setZoom(i);
break;
}
}
}
}
}
}
}

Camera Focus

Description

The nanoStream Android SDK supports camera focus and focus lock, if the internal cameras supports them. There are two non-blocking functions

setFocusArea(int focusWidth, int focusHeight, float areaMultiple, int x, int y, int previewWidth, int previewHeight, int weigh)
setFocusLockArea(int focusWidth, int focusHeight, float areaMultiple, int x, int y, int previewWidth, int previewHeight, int weigh)

through the

addFocusCalback(FocusCallback callback)
removeFocusCalback(FocusCallback callback)

you can attach or remove a FocusCallback listener. To check if your device supports focus call the function

isFocusSupported()

which will return true or false.

Parameter List

Parameter namemeaning
focusWidththe focus Area width
focusHeightthe focus Area height
areaMultiplea Multiple for the focus area (default: 1f)
xthe x position on the Screen
ythe y position on the Screen
previewWidththe width of the preview
previewHeightthe height of the preview
weightthe weight of the area must be range from 1 to 1000

FocusCallback interface

The FocusCallback interface has three abstract functions

onSuccess()
onSuccess(Rect rect, Boolean focusLock)
onFailure()

Implementation Example

public class MainActifity extens Actifity implements FocusCallback {
private GestureDetector gestureDetector;
private nanoStream streamLib = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
streamLib = new nanoStream(new nanoStreamSettings());
if(streamLib.isFocusSupported()) {
gestureDetector = new GestureDetector(this, new GestureListener());
}
...
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
if (gestureDetector != null)
{
gestureDetector.onTouchEvent(event);
}
return true;
}
....
private class GestureListener implements OnGestureListener {

@Override
public boolean onSingleTapUp(MotionEvent e)
{
if (streamLib != null)
{
streamLib.setFocusArea(300, 300, 1f, (int) e.getX(), (int) e.getY(), surface.getWidth(), surface.getHeight(), 1000);
}
return true;
}

@Override
public void onLongPress(MotionEvent e)
{
if (streamLib != null)
{
streamLib.setFocusLockArea(300, 300, 1f, (int) e.getX(), (int) e.getY(), surface.getWidth(), surface.getHeight(), 1000);
}
}
}

@Override
public void onSuccess(Rect rect, Boolean aBoolean) {
Log.i(TAG, "focus success");
}

@Override
public void onFailure() {
Log.i(TAG, "focus failed");
}
}

DeviceProperties

Before Android 4.3 there was no obligation for Android hardware manufacturers to pass the video related parts of the CTS (Compatibility Test Suite). Therefore some Android 4.1 and 4.2 Devices show non standard behaviour in regard to color format definitions and representation of video frames in memory. This could lead to issues in the video stream like switched red and blue colors, dislocated color components or a green bar at the bottom of the video frame. nanoStream Android now provides the functionality to detect and compensate common issues related to this.

Description

nanoStream.getDeviceProperties() is a static function that is running a test on the device hardware to detect non standard behaviour and returning a DeviceProperties object containing the result. DeviceProperties.getFlags() returns the test result as an integer value that can be stored in the application preferences, to avoid running the device test on every app start. DeviceProperties can be applied to a new nanoStream instance by calling nanoStream.setDeviceProperties(DeviceProperties). We recommend to call getDeviceProperties() in a background thread during the first app start on a pre 4.3 device, because the call is blocking and might last up to 5 seconds on older/weaker devices. We also recommend to store the OS version in the preferences, to be able to detect OS updates and to eventually rerun the device test or stop setting the DeviceProperties if the new OS is 4.3 or higher.

Implementation Example

public class App extends Application
{
private static DeviceProperties deviceProp = null;

public void onCreate()
{
super.onCreate();

Thread chkThread = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
int curApiVer = android.os.Build.VERSION.SDK_INT;
int curAppVer = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
int curEncVer = DeviceProperties.VERSION;

int oldApiVer = prefs.getInt("Pref_Android_API", 0);
int oldAppVer = prefs.getInt("Pref_App_Version", 0);
int oldChkVer = prefs.getInt("Pref_Check_Version", 0);
int oldChkResult = prefs.getInt("Pref_Check_Result", -1);

if (((oldApiVer * oldAppVer * oldApiVer) == 0)
|| (oldApiVer < curApiVer)
|| (oldAppVer < curAppVer)
|| (oldChkVer < curEncVer)
|| oldChkResult < 0)
{

Editor edit = prefs.edit();
edit.putInt("Pref_Android_API", curApiVer);
edit.putInt("Pref_App_Version", curAppVer);

/* Run device check */
try
{
deviceProp = nanoStream.getDeviceProperties();

edit.putInt("Pref_Check_Result", deviceProp.getFlags());
edit.putInt("Pref_Check_Version", deviceProp.getVersion());
edit.commit();
}
catch (RuntimeException e)
{
Log.d("Device Check failed", e.toString());
edit.putInt("Pref_Check_Result", -1);
edit.putInt("Pref_Check_Version", 0);
edit.commit();
}

}
else
{
deviceProp = new DeviceProperties(oldChkResult);
}

Log.d("Device Properties: ", deviceProp.toString());
}
catch (Exception e)
{
Log.d(this.getClass().getName(), "Device Check Runnable");
e.printStackTrace();
}
}
});

if (android.os.Build.VERSION.SDK_INT < 18)
{
chkThread.start();
}

...
}

public static DeviceProperties getDeviceProperties()
{
return deviceProp;
}
}
public class MainActivity extends Activity implements NanostreamEventListener
{
...
@Override
protected void onCreate(Bundle savedInstanceState)
{
try
{
nanoStreamSettings nss = new nanoStreamSettings();

streamLib = new nanoStream(nss);

DeviceProperties deviceProperties = App.getDeviceProperties();

if(null != streamLib && null != deviceProperties)
{
streamLib.setDeviceProperties(deviceProperties);
}
}
catch(NanostreamException en)
{
Toast.makeText(getApplicationContext(), en.toString(), Toast.LENGTH_LONG).show();
}
}
...
}
}