Stream mixer¶
Overview¶
WCS allows mixing streams of active broadcasts. The output stream of the mixer can be recorded, played or republished using any of technologies supported by WCS. Mixing is controlled using settings and REST API.
Warning
Stream transcoding is applied while mixing streams. The recommended server configuration is 2 CPU cores per 1 mixer.
Supported protocols of input streams¶
- WebRTC
- RTMP
- RTSP
- MPEG-TS
Output stream control capabilities¶
The mixer allows custom placing of video streams in the output frame. The stream with a certain name (desktop
by default) is considered as screensharing and is placed in the top center of the frame:
Automatically create a mixer when publishing the stream¶
If the name of the published stream has the #
symbol, the server treats everything after that symbols as the name of the mixer that will be created when the stream is published. For example, for the user1#room1
stream, the room1
mixer is created, and the stream is added to this mixer then. The stream name can also include the screen sharing keyword, for example, user1_desktop#room1
Operation flowchart¶
- The browser connects to the server via the Websocket protocol and sends the
publishStream
command. - The browser sends the WebRTC
stream1
to the server. - Flash Player establishes a connection via RTMP and sends the
publish
command. - Flash Player sends the RTMP
stream2
to the server. - The REST client creates a mixer with the output
stream3
using the query:/mixer/startup
- The REST client adds
stream1
to the mixer - The REST client adds
stream2
to the mixer - The second browser establishes a connection via Websocket and sends the
playStream
command. - The second browser receives the WebRTC stream
stream3
and plays that stream on the page.
REST API¶
A REST query must be an HTTP/HTTPS POST query in the following form:
- HTTP:
http://streaming.flashphoner.com:8081/rest-api/mixer/startup
- HTTPS:
https://streaming.flashphoner.com:8444/rest-api/mixer/startup
Here:
streaming.flashphoner.com
- is the address of the WCS server8081
- the standard REST / HTTP port of the WCS server8444
- the standard HTTPS portrest-api
- the required prefix/mixer/startup
- the REST-method used
REST methods and responses¶
/mixer/startup¶
Create a stream mixer
Request example¶
POST /rest-api/mixer/startup HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri": "mixer://mixer1",
"localStreamName": "stream3",
"hasVideo": true,
"hasAudio": true,
"watermark": "watermark.png",
"background": "background.png",
"mixerLayoutClass": "com.flashphoner.mixerlayout.TestLayout"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
409 | Conflict |
500 | Internal error |
/mixer/add¶
Add the stream to the mixer
Request example¶
POST /rest-api/mixer/add HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri": "mixer://mixer1",
"remoteStreamName": "stream1",
"hasVideo": "true",
"hasAudio": "true",
"streamLabel": "John Doe",
"videoPositionId": "speaker"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
409 | Conflict |
500 | Internal error |
/mixer/remove¶
Remove the stream from the mixer
Request example¶
POST /rest-api/mixer/remove HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri": "mixer://mixer1",
"remoteStreamName": "stream1"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
500 | Internal error |
/mixer/find_all¶
Find all mixers
Request example¶
Response example¶
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
[
{
"localMediaSessionId": "ce92b134-2468-4460-8d06-1ea3c5aabace",
"remoteMediaSessionId": null,
"localStreamName": "mixer1",
"remoteStreamName": null,
"uri": "mixer://mixer1",
"status": "PROCESSED_LOCAL",
"mediaSessions": [
"95bf2be8-f459-4f62-9a7f-c588f33e0ad3",
"693781de-cada-4589-abe1-c3ee55c66901"
],
...
}
]
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
500 | Internal error |
/mixer/terminate¶
Stop the mixer
Request example¶
POST /rest-api/mixer/terminate HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri": "mixer://mixer1"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
500 | Internal error |
/mixer/setAudioVideo¶
Mute/unmute video or change audio level for mixer incoming stream
Request example¶
POST /rest-api/mixer/setAudioVideo HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri": "mixer://m1",
"streams": "^stream.*",
"audioLevel": 0,
"videoMuted": true
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
500 | Internal error |
/mixer/set_body_watermark¶
Add watermark to mixer output stream picture
Request example¶
POST /rest-api/mixer/set_body_watermark HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"watermark":"/opt/media/logo.png",
"x":10,
"y":10,
"marginTop":5,
"marginLeft":5,
"marginBottom":5,
"marginRight":5
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
/mixer/set_stream_watermark¶
Add watermark to one of the mixer input streams pictures in the mixer output stream
Request example¶
POST /rest-api/mixer/set_stream_watermark HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"watermark":"/opt/media/logo.png",
"mediaSessionId": "030bb470-185c-11ed-9fad-918e05233ae9",
"x":10,
"y":10,
"marginTop":5,
"marginLeft":5,
"marginBottom":5,
"marginRight":5
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
/mixer/set_stream_label¶
Set or change mixer participant stream label to display
Request example¶
POST /rest-api/mixer/set_stream_label HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"remoteStreamName":"stream1",
"streamLabel": "Mr. John Doe"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
404 | Not found |
/mixer/set_parameter¶
Change the mixer parameter
Request example¶
POST /rest-api/mixer/set_parameter HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"mixerLayoutDir":"/opt/GridLayout"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
/mixer/set_stream_avatar¶
Set avatar picture to audio stream in mixer
Warning
The feature is supported in Ubuntu 20.04 and other systems with glibc 2.31 and newer only!
Request example¶
POST /rest-api/mixer/set_stream_avatar HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"remoteStreamName":"stream1",
"avatar": "https://mystorage/storage/avatar.png"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
500 | Internal server error |
/mixer/remove_stream_avatar¶
Remove avatar picture from audio stream in mixer
Warning
The feature is supported in Ubuntu 20.04 and other systems with glibc 2.31 and newer only!
Request example¶
POST /rest-api/mixer/remove_stream_avatar HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"remoteStreamName":"stream1"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
500 | Internal server error |
/mixer/set_position¶
Move the stream picture to the position (for custom XML layouts only)
Request example¶
POST /rest-api/mixer/set_position HTTP/1.1
Host: localhost:8081
Content-Type: application/json
{
"uri":"mixer://m1",
"remoteStreamName":"stream1",
"videoPositionId":"speaker"
}
Response example¶
Return codes¶
Code | Reason |
---|---|
200 | OK |
400 | Bad request |
404 | Not found |
500 | Internal server error |
Parameters¶
Parameter name | Description | Example |
---|---|---|
uri | Unique identifier of the mixer |
mixer://mixer1
|
localStreamName | Name of the output stream of the mixer |
stream3
|
hasVideo | Mix video |
true
|
hasAudio | Mix audio |
true
|
remoteStreamName | Name of the stream added to the mixer |
stream1 rtmp://rtmp.flashphoner.com:1935/live/rtmp_stream1
|
mediaSessionId | Media session identifier |
ce92b134-2468-4460-8d06-1ea3c5aabace
|
status | Stream status |
PROCESSED_LOCAL
|
background | Mixer background |
background.png
|
watermark | Mixer watermark |
watermark.png
|
mixerLayoutClass | Mixer layout |
com.flashphoner.mixerlayout.TestLayout
|
streams | Streams list or regular expression for search |
^stream.* ["stream1", "stream2"]
|
audioLevel | Incoming stream audio level |
0
|
videoMuted | Mute video |
true
|
streamLabel | Mixer participant stream label to display |
John Doe
|
avatar | Picture URI in PNG, JPG, BMP formats |
https://mystorage.com/storage/avatar.jpg
|
videoPositionId | Position identifier in custom XML layout to place a stream picture |
speaker
|
Mixer configuration while creating the instance using REST API¶
Since build 5.2.872 it is possible to pass most of mixer parameters corresponding
to flashphoner.properties mixer settings while creating the mixer with /mixer/startup
REST query. In this case, parameters will be applied to the created mixer instance only. For example, the following query
{
"uri": "mixer://mixer1",
"localStreamName": "stream3",
"mixerVideoWidth": 640,
"mixerVideoHeight": 360,
"mixerVideoFps": 24,
"mixerVideoBitrateKbps": 500
}
will create the mixer with output stream resolution 640x360, fps 24 and bitrate 500 kbps, no matter what settings are set in flashphoner.properties file
The mixer parameters full list can be retrieved using /mixer/find\_all
REST query, for example
Mixer parameters full list
[
{
"localMediaSessionId": "7c9e5353-8680-4ad2-8a47-1a366091785c",
"localStreamName": "m1",
"uri": "mixer://m1",
"status": "PROCESSED_LOCAL",
"hasAudio": true,
"hasVideo": true,
"record": false,
"mediaSessions": [],
"mixerLayoutClass": "com.flashphoner.media.mixer.video.presentation.GridLayout",
"mixerLayoutDir": "/opt/mixer1-layout",
"mixerActivityTimerCoolOffPeriod": 1,
"mixerActivityTimerTimeout": -1,
"mixerAppName": "defaultApp",
"mixerAudioOpusFloatCoding": false,
"mixerAudioSilenceThreshold": -50,
"mixerAudioThreads": 4,
"mixerAutoScaleDesktop": true,
"mixerDebugMode": false,
"mixerDesktopAlign": "TOP",
"mixerDisplayStreamName": false,
"mixerDecodeStreamName": false,
"mixerFontSize": 20,
"mixerFontSizeAudioOnly": 40,
"mixerIdleTimeout": 20000,
"mixerInBufferingMs": 200,
"mixerIncomingTimeRateLowerThreshold": 0.95,
"mixerIncomingTimeRateUpperThreshold": 1.05,
"mixerMcuAudio": false,
"mixerMcuVideo": false,
"mixerMcuMultithreadedMix": false,
"mixerMinimalFontSize": 1,
"mixerMcuMultithreadedDelivery": false,
"mixerOutBufferEnabled": false,
"mixerOutBufferInitialSize": 2000,
"mixerOutBufferStartSize": 150,
"mixerOutBufferPollingTime": 100,
"mixerOutBufferMaxBufferingsAllowed": -1,
"mixerShowSeparateAudioFrame": true,
"mixerTextAutoscale": true,
"mixerTextColour": "0xFFFFFF",
"mixerTextBulkWriteWithBuffer": true,
"mixerTextBulkWrite": true,
"mixerTextBackgroundOpacity": 100,
"mixerTextBackgroundColour": "0x2B2A2B",
"mixerTextPaddingLeft": 5,
"mixerVoiceActivitySwitchDelay": 0,
"mixerVoiceActivityFrameThickness": 6,
"mixerVoiceActivityFramePositionInner": false,
"mixerVoiceActivityColour": "0x00CC66",
"mixerVoiceActivity": true,
"mixerVideoWidth": 1280,
"mixerVideoThreads": 4,
"mixerVideoStableFpsThreshold": 15,
"mixerVideoQuality": 24,
"mixerVideoProfileLevel": "42c02a",
"mixerVideoLayoutDesktopKeyWord": "desktop",
"mixerVideoHeight": 720,
"mixerVideoGridLayoutPadding": 30,
"mixerVideoGridLayoutMiddlePadding": 10,
"mixerVideoFps": 30,
"mixerVideoDesktopLayoutPadding": 30,
"mixerVideoDesktopLayoutInlinePadding": 10,
"mixerVideoBufferLength": 1000,
"mixerVideoBitrateKbps": 2000,
"mixerUseSdpState": true,
"mixerType": "NATIVE",
"mixerThreadTimeoutMs": 33,
"mixerTextPaddingTop": 5,
"mixerTextPaddingRight": 4,
"mixerTextFont": "Serif",
"mixerTextPaddingBottom": 5,
"mixerTextDisplayRoom": true,
"mixerTextCutTop": 3,
"mixerRealtime": true,
"mixerPruneStreams": false,
"audioMixerOutputCodec": "alaw",
"audioMixerOutputSampleRate": 48000,
"audioMixerMaxDelay": 300,
"mixerAudioOnlyHeight": 360,
"mixerAudioOnlyWidth": 640,
"videoOutputCodec":"vp8",
"mixerTextAlign": "TOP_CENTER"
}
]
Changing mixer parameters on the fly by REST API¶
Since build 5.2.1480, the following parameters can be changed for an active mixer using /mixer/set_parameter
REST query:
[
{
...
"mixerLayoutClass": "com.flashphoner.media.mixer.video.presentation.GridLayout",
"mixerAutoScaleDesktop": true,
"mixerDesktopAlign": "TOP",
"mixerDisplayStreamName": true,
"mixerFontSize": 20,
"mixerFontSizeAudioOnly": 40,
"mixerMinimalFontSize": 1,
"mixerShowSeparateAudioFrame": true,
"mixerTextAutoscale": true,
"mixerTextColour": "0xFFFFFF",
"mixerTextBulkWriteWithBuffer": true,
"mixerTextBulkWrite": true,
"mixerTextBackgroundOpacity": 100,
"mixerTextBackgroundColour": "0x2B2A2B",
"mixerFrameBackgroundColour": "0x2B2A2B",
"mixerTextPaddingLeft": 5,
"mixerVoiceActivitySwitchDelay": 0,
"mixerVoiceActivityFrameThickness": 6,
"mixerVoiceActivityFramePositionInner": false,
"mixerVoiceActivityColour": "0x00CC66",
"mixerVoiceActivity": true,
"mixerVideoLayoutDesktopKeyWord": "desktop",
"mixerVideoGridLayoutPadding": 30,
"mixerVideoGridLayoutMiddlePadding": 10,
"mixerVideoDesktopLayoutPadding": 30,
"mixerVideoDesktopLayoutInlinePadding": 10,
"mixerTextPaddingTop": 5,
"mixerTextPaddingRight": 4,
"mixerTextFont": "Serif",
"mixerTextPaddingBottom": 5,
"mixerTextDisplayRoom": true,
"mixerTextCutTop": 3,
"mixerTextAlign": "BOTTOM_LEFT",
"mixerVideoDesktopFullscreen": false,
"mixerLayoutDir": ""
}
]
If the REST query contains a parameters which cannot be set for active mixer, it will return 400 Bad request
with unsupported parameters list.
Sending the REST query to the WCS server¶
To send the REST query to the WCS server use a REST-client в браузере, curl
to send a query from a script, or appropriate function of the application framework.
Known limits¶
Mixer output stream name must be defined while creating mixer by REST API (localStreamName
parameter). Otherwise, server returns 400 Bad request
with message No localStreamName given
.
Configuration¶
Automatic mixer creation configuration¶
Automatic creation of mixers for streams with the #
symbol in their name requires the REST hook application that handles input streams to register the handler com.flashphoner.server.client.handler.wcs4.FlashRoomRecordingStreamingHandler
. Registering the handler can be done using the command line interface. For instance, for the flashStreamingApp
application used to publish incoming RTMP streams this can be done with the following command:
update app -m com.flashphoner.server.client.handler.wcs4.FlashRoomRecordingStreamingHandler -c com.flashphoner.server.client.handler.wcs4.FlashStreamingCallbackHandler flashStreamingApp
You can read more about managing applications using the command line of the WCS server here.
Audio and video mixing configuration¶
By default, both video and audio streams are mixed. If audio only mixing is necessary, it should be set on mixer creation
To switch off video mixing for all streams, this parameter should be set in flashphoner.properties file
In this case video mixing can be switched on for certain mixer on its creation.
Since build 5.2.689 audio mixing can be enabled or disabled on mixer creation
or in server settings for all the streams
Publishing a stream without audio or video track to AV mixer¶
When a stream with audio or video track only is published to AV mixer, it is necessary to disable RTP activity control with the following parameters
Mixer output stream bufferization¶
In some cases, mixer output stream bufferization is needed. This feature is enabled with the following parameter in flashphoner.properties file
The buffer size is defined in milliseconds with parameter
In this case, the buffer size is 400 ms.
Stream data fetching from buffer and sending period is defined in milliseconds with parameter
In this case, the period is 20 ms.
Changing bitrate of mixer output stream¶
When OpenH264 codec is used for transcoding, it is possible to change bitrate of mixer output stream with the following parameter in [flashphoner.properties file
By default, mixer output stream bitrate is set to 2 Mbps. If a channel bandwidth between server and viewer is not enough, bitrate can be reduced, for example
If picture quality with default bitrate is low, or distortion occurs, it is recommended to rise mixer output stream bitrate to 3-5 Mbps
Mixer output stream video codec management¶
Since build 5.2.1075 it is possible to set mixer output stream video codec. H264 is used by default
VP8 codec can be set with the following parameter
Note that keyframes in this case will be sent less frequently comparing to H264 stream because VP8 key frame size is significally bigger. This can lead to a slight delay playing the first frame in browser comparing to H264 stream with the same parameters.
Mixer output stream sound management¶
By default, mixer output stream sound is encoded to Opus with sample rate 48 kHz. These settings may be changed using the parameters in flashphoner.properties file. For example, to use mixer output stream in SIP call the following value can be set:
In this case, sound will be encoded to PCMA (alaw) with sample rate 8 kHz.
Using custom lossless videoprocessor for incoming streams handling¶
To handle mixer incoming streams, if additional bufferizing or audio and video tracks synchronizing is required for example, the custom lossless videoprocessor may be used. This feature is enabled with the following parameter in flashphoner.properties
The maximum size of mixer buffer in milliseconds is set with this parameter
By default, maximum mixer buffer size is 200 ms. After filling this buffer, the custom lossless videoprocessor uses its own buffer and waits for mixer buffer freeing. The period of mixer buffer checking is set in milliseconds with this parameter
By default, the mixer buffer checking period is 20 ms.
Warning
Using the custom lossless videoprocessor may degrade real-time performance.
When custom lossless videoprocessor is used, it is necessary to stop mixer with REST query /mixer/terminate
to free all consumed resources. Mixer can be stopped also by stopping all incoming streams, in this case mixer will stop when following timeout in milliseconds expires
By default, mixer will stop after 60 seconds if there are no active incoming streams.
Mixer output stream layout management¶
By default, three mixer output stream layouts are implemented:
Grid layout¶
This layout can be enabled with the following parameter in flashphoner.properties file
Zero padding grid layout¶
This layout can be enabled with the following parameter
and works for input streams of equal resolution with the same aspect ratio only.
Crop around center zero padding grid layout¶
Since build 5.2.842, it is possible to crop participant's video around its center. That can be useful in case of video conference, when participant's face is located in the centeral part.
This layout can be enabled with the following parameter
Desktop (screen sharing) layout¶
This layout is enabled if one of mixer input streams has a name containing keyword defined in the following parameter
By default, a stream should have desktop
substring in its name to use it as screen sharing stream, for example user1_desktop
Since build 5.2.710 it is possible to change screen sharing picture placement with the following parameter:
By default, screen sharing picture is placed above the other pictures
The following placements are supported:
Placement | Description |
---|---|
TOP |
Screen above |
LEFT |
Screen on left side |
RIGHT |
Sreen on right side |
BOTTOM |
Screen below |
CENTER |
Screen in center surronded by other pictures |
For example, the following parameter
places the screen picture on the right side of mixer output stream picture
If a number of screen sharing streams are published to the same mixer, the first published stream take the main place. If the stream stops after that, the main place will be taken by the following screen share stream in alphabetical order.
Picture in picture¶
This layout is added since build 5.2.852. In this case a stream that has a name with the keyword defined in the following parameter
for example user1_desktop
, will be background for other streams in mixer. This layout can be enabled with the following parameter
Custom mixer layout implementation¶
For more fine tuning of mixer layout, custom Java class should be developed to implement IVideoMixerLayout
interface, for example
TestLayout.java
// Package name should be strictly defined as com.flashphoner.mixerlayout
package com.flashphoner.mixerlayout;
// Import mixer layout interface
import com.flashphoner.sdk.media.IVideoMixerLayout;
// Import YUV frame description
import com.flashphoner.sdk.media.YUVFrame;
// Import Java packages to use
import java.awt.*;
import java.util.ArrayList;
/**
* Custom mixer layout implementation example
*/
public class TestLayout implements IVideoMixerLayout {
// Pictures padding, should be even (or zero if no padding needed)
private static final int PADDING = 4;
/**
* Function to compute layout, will be called by mixer before encoding output stream picture
* This example computes grid layout
* @param yuvFrames - incoming streams raw pictures array in YUV format
* @param strings - incoming streams names array
* @param canvasWidth - mixer output picture canwas width
* @param canvasHeight - mixer output picture canwas heigth
* @return array of pictures layouts
*/
@Override
public Layout[] computeLayout(YUVFrame[] yuvFrames, String[] strings, int canvasWidth, int canvasHeight) {
// Declare picture layouts list to fill
ArrayList<IVideoMixerLayout.Layout> layout = new ArrayList<>();
// Find canvas center height
int canvasCenter = canvasHeight / 2;
// Find frame center
int frameCenter = canvasCenter - (canvasHeight / yuvFrames.length) / 2;
// Find every picture dimensions (this are the same for all the pictures because this is grid layout)
int layoutWidth = canvasWidth / yuvFrames.length - PADDING;
int layoutHeight = canvasHeight / yuvFrames.length;
// Iterate through incoming stream pictures array
// Note: streams pictures order corresponds to stream names array, use this to reorder pictures if needed
for (int c = 0; c < yuvFrames.length; c++) {
// Use Java AWT Point and Dimension
Point prevPoint = new Point();
Dimension prevDimension = new Dimension();
if (layout.size() > 0) {
// Find previous picture location to calculate next one
prevPoint.setLocation(layout.get(c - 1).getPoint());
prevDimension.setSize(layout.get(c - 1).getDimension());
}
// Set starting point of the picture
Point currentPoint = new Point((int) (prevPoint.getX() + prevDimension.getWidth() + PADDING),
frameCenter);
// Create the picture layout passing starting point, dimensions and raw picture YUV frames
layout.add(new IVideoMixerLayout.Layout(currentPoint, new Dimension(layoutWidth,
layoutHeight), yuvFrames[c]));
}
// Return pictures layouts calculated as array
return layout.toArray(new IVideoMixerLayout.Layout[layout.size()]);
}
}
To support caption text location above or below stream picture, a special Box
class should be used to calculate pictures placement since build 5.2.878, for example
TestLayoutWithBox.java
// Package name should be strictly defined as com.flashphoner.mixerlayout
package com.flashphoner.mixerlayout;
// Import mixer layout interface
import com.flashphoner.media.mixer.video.presentation.BoxPosition;
import com.flashphoner.sdk.media.IVideoMixerLayout;
// Import YUV frame description
import com.flashphoner.sdk.media.YUVFrame;
// Import Box class for picture operations
import com.flashphoner.media.mixer.video.presentation.Box;
// Uncomment this if using build 5.2.878-5.2.976
// import com.flashphoner.server.commons.rmi.data.impl.MixerConfig;
// Import Java packages to use
import java.awt.*;
import java.util.ArrayList;
/**
* Custom mixer layout implementation example
*/
public class TestLayout implements IVideoMixerLayout {
// Pictures padding, should be even (or zero if no padding needed)
private static final int PADDING = 4;
/**
* Function to compute layout, will be called by mixer before encoding output stream picture
* This example computes one-line grid layout
* @param yuvFrames - incoming streams raw pictures array in YUV format
* @param strings - incoming streams names array
* @param canvasWidth - mixer output picture canwas width
* @param canvasHeight - mixer output picture canwas heigth
* @return array of pictures layouts
*/
@Override
public Layout[] computeLayout(YUVFrame[] yuvFrames, String[] strings, int canvasWidth, int canvasHeight) {
// This object represents mixer canvas
Box mainBox = new Box(null, canvasWidth, canvasHeight);
// Find every picture dimensions (this are the same for all the pictures because this is grid layout)
int frameBoxWidth = canvasWidth / yuvFrames.length - PADDING;
int frameBoxHeight = canvasHeight / yuvFrames.length;
// Container to place stream pictures
Box container = new Box(mainBox, canvasWidth, frameBoxHeight);
container.setPosition(BoxPosition.CENTER);
// Iterate through incoming stream pictures array
// Note: streams pictures order corresponds to stream names array, use this to reorder pictures if needed
boolean firstFrame = true;
for (int c = 0; c < yuvFrames.length; c++) {
// Stream picture parent rectangle
// This rectangle can include stream caption if mixer_text_outside_frame setting is enabled
Box frameBox = new Box(container, frameBoxWidth, (int)container.getSize().getHeight());
frameBox.setPosition(BoxPosition.INLINE_HORIZONTAL_CENTER);
// Add padding for subsequent pictures
if (!firstFrame) {
frameBox.setPaddingLeft(PADDING);
}
firstFrame = false;
// Compute picture frame placement including stream caption text
Box frame = Box.computeBoxWithFrame(frameBox, yuvFrames[c]);
// Adjust picture to the parent rectangle
frame.fillParent();
}
// Prepare an array to return layout calculated
ArrayList<IVideoMixerLayout.Layout> layout = new ArrayList<>();
// Calculate mixer layout
mainBox.computeLayout(layout);
// Return the result
return layout.toArray(new IVideoMixerLayout.Layout[layout.size()]);
}
/**
* The function for internal use.
* Uncomment this if using build 5.2.878-5.2.976
*/
//@Override
//public void setConfig(MixerConfig mixerConfig) {
//}
}
Main Box
class methods:
Box methods available
/**
* Set paddings
* @param padding padding value to set
**/
public void setPaddingLeft(int paddingLeft);
public void setPaddingRight(int paddingRight);
public void setPaddingTop(int paddingTop);
public void setPaddingBottom(int paddingBottom);
/**
* Set position
* @param position box position to set
**/
public void setPosition(BoxPosition position);
/**
* Compute location and size of this box and all of it's content
* @param layouts aggregator collection to put computed layouts to
*/
public void computeLayout(ArrayList<IVideoMixerLayout.Layout> layouts);
/**
* Compute box location inside the parent Box including caption text placement
* @param parent parent box
* @param yuvFrame frame to compute location
* @return frame with location computed
*/
public static Box computeBoxWithFrame(Box parent, YUVFrame yuvFrame);
/**
* Scale box to the size closest to size of the parent, preserving aspect ratio
*/
public void fillParent();
/**
* Sets box size without saving aspect ratio, crops image from center
*/
public void fillParentNoScale();
Possible positions to place Box
object:
BoxPosition
public enum BoxPosition {
//attach box to the left
LEFT,
//attach box to the right
RIGHT,
//attach box to the top
TOP,
//attach box to the bottom
BOTTOM,
//center box in the parent box
CENTER,
//bottom and center box in the parent box
BOTTOM_CENTER,
//top and center box in the parent box
TOP_CENTER,
//attach box to the right-top corner of left adjoining box (in the same parent box)
INLINE_HORIZONTAL,
//same as INLINE_HORIZONTAL but additionally adds vertical CENTER
INLINE_HORIZONTAL_CENTER,
//attach box to the left-bottom corner of upper adjoining box (in the same parent box)
INLINE_VERTICAL,
//same as INLINE_VERTICAL but additionally adds horizontal CENTER
INLINE_VERTICAL_CENTER
}
Then the class should be complied into byte code. To do this, create folder tree accordind to TestLayout class package name
and execute the command
javac -cp /usr/local/FlashphonerWebCallServer/lib/wcs-core.jar ./com/flashphoner/mixerlayout/TestLayout.java
Now, pack the code compiled to jar file
and copy this file to WCS libraries folder
To use custom mixer layout class, set it to the following parameter in flashphoner.properties file
and restart WCS.
With this custom layout, mixer output stream for three input streams will look like:
Cropping pictures in custom layout¶
To crop a picture around a central point like CropNoPaddingGridLayout
, use Box.fillParentNoScale()
instead of Box.fillParent()
method. The following example places two pictures by name with cropping around center:
SideBySideLayout.java
package com.flashphoner.mixerlayout;
// Import mixer layout interface
import com.flashphoner.media.mixer.video.presentation.BoxPosition;
import com.flashphoner.sdk.media.IVideoMixerLayout;
// Import YUV frame description
import com.flashphoner.sdk.media.YUVFrame;
// Import Box class for picture operations
import com.flashphoner.media.mixer.video.presentation.Box;
// Uncomment this if using build 5.2.878-5.2.976
// import com.flashphoner.server.commons.rmi.data.impl.MixerConfig;
// Import Java packages to use
import java.awt.*;
import java.util.ArrayList;
public class SideBySideLayout implements IVideoMixerLayout {
// Owner's stream name
private static final String USERFOR = "user_for";
// Challenger's stream name
private static final String USERAGAINST = "user_against";
/**
* Function to compute layout, will be called by mixer before encoding output stream picture
* This example computes grid layout
* @param yuvFrames - incoming streams raw pictures array in YUV format
* @param strings - incoming streams names array
* @param canvasWidth - mixer output picture canwas width
* @param canvasHeight - mixer output picture canwas heigth
* @return array of pictures layouts
*/
@Override
public Layout[] computeLayout(YUVFrame[] yuvFrames, String[] strings, int canvasWidth, int canvasHeight) {
// This object represents mixer canvas
Box mainBox = new Box(null, canvasWidth, canvasHeight);
// Container to place CHALLENGER stream pictures
Box userForContainer = new Box(mainBox, canvasWidth / 2, canvasHeight);
userForContainer.setPosition(BoxPosition.LEFT);
// Container to place OWNER stream pictures
Box userAgainstContainer = new Box(mainBox, canvasWidth / 2, canvasHeight);
userAgainstContainer.setPosition(BoxPosition.RIGHT);
// Iterate through incoming stream pictures array
for (int c = 0; c < yuvFrames.length; c++) {
String name = strings[c];
Box container;
// Chhose container depending on stream name
if (name.contains(USERFOR)) {
container = userForContainer;
} else if (name.contains(USERAGAINST)) {
container = userAgainstContainer;
} else {
// Wrong stream name
continue;
}
// Fill the container by the stream picture
Box frameBox = Box.computeBoxWithFrame(container, yuvFrames[c]);
frameBox.fillParentNoScale();
}
// Prepare an array to return layout calculated
ArrayList<IVideoMixerLayout.Layout> layout = new ArrayList<>();
// Calculate mixer layout
mainBox.computeLayout(layout);
// Return the result
return layout.toArray(new IVideoMixerLayout.Layout[layout.size()]);
}
/**
* The function for internal use.
* Uncomment this if using build 5.2.878-5.2.976
*/
//@Override
//public void setConfig(MixerConfig mixerConfig) {
//}
}
A separate folder for custom Java libraries¶
Since build 5.2.1512, custom layout Java libraries (jar files) should be placed to the folder /usr/local/FlashphonerWebCallServer/lib/custom
This folder is kept while updating WCS to a newer builds. A jar files do not need to be copied again after updating.
Mixer layout management while creating mixer¶
Since build 5.2.693 mixer layout can be defined when creating mixer with REST API, for example
{
"uri": "mixer://mixer1",
"localStreamName": "mixer1",
"mixerLayoutClass": "com.flashphoner.mixerlayout.TestLayout"
}
Then, layout can be defined for every mixer separately
Mixer output stream encoding profile management¶
Some browsers do not support playback for H264 streams encoded by certain profiles. To solve it, the following parameter is added since build 5.2.414 to set mixer output stream encoding profile
By default, constrainted baseline level 4.2 profile is set.
Mixer background management and watermarking¶
Since build 5.2.693 mixer backgroung can be defined and watermark can be added when creating mixer with REST API, for example
{
"uri": "mixer://mixer1",
"localStreamName": "mixer1",
"watermark": "watermark.png",
"background": "background.png"
}
By default, files should be placed to /usr/local/FlashphonerWebCallServer/conf
folder. Full path to the files can also be set, for example
{
"uri": "mixer://mixer1",
"localStreamName": "mixer1",
"watermark": "/opt/media/watermark.png",
"background": "/opt/media/background.png"
}
Adding and changing stream watermark dynamically¶
Since build 5.2.1349 in is possible to dynamically add or change stream watermark without stopping the mixer. A watermark can be added, changed or moved to another picture location according to coordinates defined using REST API queries:
-
/mixer/set_body_watermark
- to the whole mixer output stream picture
-
/mixer/set_stream_watermark
- to a certain input stream picture
Where
watermark
- watermark file namex
,y
- top left watermark corner coordinates on the stream picturemarginTop
,marginLeft
,marginBottom
,marginRignt
- watermark margins from stream picture borders
If watermark coordinates are out of stream picture bounds, the watermark will be scaled to the bounds using margins.
To move watermark to another location on the stream picture, send the query with the same file name and a new coordinates. To remove watermark from the stream picture, send the query with empty watermark
field
Stereo sound in mixer output stream¶
By default, mixer converts stereo audio from incoming streams to mono to reduce the amiunt of processed data. This allows to minimize a possible delay while using realtime mixer in video conferencing case.
Mixer can be switched to stereo sound processing if necessary, for example, in case of online music radio. Since build 5.2.922 it is possible to set mixer audio channels count usig the following parameter
Characters decoding in input stream name¶
Since build 5.2.1802 it is possible to decode a characters encoded by encodeURIComponent() in input stream name. The feature may be enabled by the following parameter
or by the following /mixer/startup
query parameter
In this case a decoded characters available in the font used will be displayed, or a similar characters.
MCU support¶
Mixer MCU support for audio can be enabled with the following parameter
In this case for three input streams stream1
, stream2
, stream3
the following output streams will be generated in mixer mixer1
:
Output stream name | stream1 | stream2 | stream3 | |||
audio | video | audio | video | audio | video | |
mixer1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
mixer1-stream1 | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
mixer1-stream2 | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
mixer1-stream3 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
Thus, each of the additional streams contains audio of all streams in the mixer, except for one. This allows for example to eliminate echo for conference participants.
This feature can be also enabled for video with the following parameter
In this case for three input streams stream1
, stream2
, stream3
the following output streams will be generated in mixer mixer1
:
Output stream name | stream1 | stream2 | stream3 | |||
audio | video | audio | video | audio | video | |
mixer1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
mixer1-stream1 | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
mixer1-stream2 | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
mixer1-stream3 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Thus, each of the additional streams contains audio and video of all streams in the mixer, except for one. This allows to arrange a full-fledged chat room based on mixer.
In this case, if mixer recording is enabled with the parameter
the main mixer output stream will be only recorded (mixer1
in the example above).
Incoming streams audio and video management¶
Since build 5.2.835 it is possible to change audio level and mute video for mixer incoming streams. In this case, the original stream remains unchanged. Video track can be muted (black screen) and then unmuted. For audio track, volume level can be set in percent up to 100, or sound can be muted by setting level to 0.
Incoming streams are managed using REST API query /mixer/setAudioVideo
.
For example, create a mixer and add 3 streams to it: 2 participants and 1 speaker
curl -H "Content-Type: application/json" -X POST http://localhost:8081/rest-api/mixer/startup -d '{"uri": "mixer://m1", "localStreamName":"m1"}'
curl -H "Content-Type: application/json" -X POST http://localhost:8081/rest-api/mixer/add -d '{"uri": "mixer://m1", "remoteStreamName": "stream1"}'
curl -H "Content-Type: application/json" -X POST http://localhost:8081/rest-api/mixer/add -d '{"uri": "mixer://m1", "remoteStreamName": "stream2"}'
curl -H "Content-Type: application/json" -X POST http://localhost:8081/rest-api/mixer/add -d '{"uri": "mixer://m1", "remoteStreamName": "desktop"}'
Mute all participants excluding speaker
POST /rest-api/mixer/setAudioVideo HTTP/1.1
User-Agent: curl/7.29.0
Host: localhost:8081
Content-Type: application/json
Content-Length: 62
{
"uri": "mixer://m1",
"streams": "^stream.*",
"audioLevel": 0
}
REST query streams
parameter may be set either as regular expression to match streams by name, or as streams list. Use regular expression syntax supported by Java, the examples can be found here.
Mute stream1
video
POST /rest-api/mixer/setAudioVideo HTTP/1.1
User-Agent: curl/7.29.0
Host: localhost:8081
Content-Type: application/json
Content-Length: 65
{
"uri": "mixer://m1",
"streams": ["stream1"],
"videoMuted": true
}
Check streams state by /mixer/find_all
query
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Content-Type: application/json
Content-Length: 574
[
{
"localMediaSessionId": "e2fa5c8b-16f3-4917-9d5f-557dde75db07",
"localStreamName": "m1",
"uri": "mixer://m1",
"status": "PROCESSED_LOCAL",
"hasAudio": true,
"hasVideo": true,
"record": false,
"mediaSessions": [
{
"localMediaSessionId": "3dd763b0-2ae7-11eb-aa72-37b2cbcbf6b9",
"audioLevel": 0,
"videoMuted": true,
"localStreamName": "stream1"
},
{
"localMediaSessionId": "8af64760-2ae7-11eb-b086-cdf035231b9d",
"audioLevel": 100,
"videoMuted": false,
"localStreamName": "desktop"
},
{
"localMediaSessionId": "7cc4b410-2ae7-11eb-b34c-a5240fe9f151",
"audioLevel": 0,
"videoMuted": false,
"localStreamName": "stream2"
}
]
}
]
Incoming audio and video management while adding stream to mixer¶
Since build 5.2.982 it is possible to mute audio or change audio level and mute video while
adding stream to mixer. To do this, the additional parameters should be set in /mixer/add REST query like in /mixer/setAudioVideo
one
POST /rest-api/mixer/add HTTP/1.1
User-Agent: curl/7.29.0
Host: localhost:8081
Content-Type: application/json
Content-Length: 85
{
"uri": "mixer://m1",
"remoteStreamName": "stream1",
"audioLevel": 0,
"videoMuted": false
}
Note that if a stream was added to mixer with hasVideo: false
parameter
then videoMuted
parameter will be ignored, the stream will always be audio only in the mixer.
Quick manual on testing¶
-
For this test we use:
- the demo server at demo.flashphoner.com;
- the Chrome browser and the REST-client to send queries to the server;
- the Two Way Streaming web application to publish input streams of the mixer;
- the Player web application to play the output stream of the mixer.
-
Open the page of the Two Way Streaming application. Publish the stream named
stream1
:
-
In another tab open the page of the Two Way Streaming application. Publish the stream named
desktop
:
-
Open the REST client. Send the
/mixer/startup
query and specify the URI of the mixermixer://mixer1
and the output stream namestream3
in its parameters:
-
Send the
/mixer/add
query and specify the URI of the mixermixer://mixer1
and the input stream namestream1
in its parameters:
-
Open the Player web application, specify the name of the output stream of the mixer
stream3
in theStream
field and clickStart
:
-
Send
/mixer/add
and specify the URI of the mixermixer://mixer1
and the input stream namedesktop
in its parameters:
-
In the output stream of the mixer you should see the
desktop
stream that imitates screen sharing and the streamstream1
:
Call flow¶
Below is the call flow when using the mixer.
-
Publishing of the WebRTC stream
stream1
-
Sending the WebRTC stream to the server
-
Publishing the RTMP stream
stream2
-
Sending the RTMP stream to the server
-
Sending the
/mixer/startup
query to create themixer://stream3
mixer with the outputstream3
-
Sending the
/mixer/add
query to addstream1
to themixer://stream3
mixer
-
Sending the
/mixer/add
query to addstream2
to themixer://stream3
mixer -
Playing the WebRTC stream
stream3
-
Sending the WebRTC audio stream to the client
Mixer REST hooks¶
By default, all the standard REST hooks are invoked for mixer stream. The /connect
REST hook is invoked while creating a mixer.
A separate server application can be created for mixer REST hooks handling if necessary. To send REST hooks to this application, the following parameter can be used since build 5.2.634
By default, mixer REST hooks are handled by defaultApp
like another streams REST hooks.
Known issues¶
1. Mixer name cannot contain non-URI symbols¶
A mixer is not created if the name of the mixer contains symbols restricted for URI.
Symptoms
A mixer with the name like test_mixer
does not create.
Solution
Do not use disallowed symbols in the name of a mixer or a stream, especially if automatic mixer creation option is enabled. For instance, the name user_1#my_room
cannot be used. If streams of chat rooms are mixed, room names also cannot use restricted symbols.
2. The same stream cannot be added to two or more non-realtime mixers¶
The same stream cannot be added to two or more mixers if non-realtime mixer is used
Symptoms
When the stream is added to second mixer, the first mixer output stream stops
Solution
Do not use the same stream in more than one mixer if mixer_realtime=false
3. Encoding quality settings cannot be applied if OpenH264 is used¶
Symptoms
Picture quality is not changing when using different mixer_video_quality
values, for example
4. Black picture is shown if watermark file is damaged¶
Default watermark or background (black picture) will be used if PNG file is damaged or is not a PNG
Symptoms
a) Black picture in output stream when watermark is added, there is a message in server log
b) output stream background is still black, there is a message in server log
Solution
Use only correct PNG file to add watermark or background