package org.deviceconnect.android.deviceplugin.webrtc.core;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
import org.deviceconnect.android.deviceplugin.webrtc.BuildConfig;
import org.deviceconnect.android.deviceplugin.webrtc.util.ImageUtils;
import org.deviceconnect.android.deviceplugin.webrtc.util.MixedReplaceMediaClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.EglBase;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoCapturerAndroid;
import org.webrtc.VideoCapturerObject;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
/**
* Inputs the external resource to video on WebRTC.
*
* @author NTT DOCOMO, INC.
*/
public class VideoCapturerExternalResource implements VideoCapturerObject {
/**
* Flag for debugging.
*/
private final static boolean DEBUG = BuildConfig.DEBUG;
/**
* Tag for debugging.
*/
private final static String TAG = "VideoExternal";
private int mWidth;
private int mHeight;
private int mFPS;
/**
* Observer to send the buffer of capture image.
*/
private VideoCapturerAndroid.CapturerObserver mFrameObserver;
/**
* Width to request.
*/
private int mRequestWidth;
/**
* Height to request.
*/
private int mRequestHeight;
/**
* This client to get the image from the server.
*/
private MixedReplaceMediaClient mClient;
/**
* Uri of resource.
*/
private String mUri;
/**
* Lock object for updating the image.
*/
private final Object mLockObj = new Object();
private SurfaceTextureHelper mSurfaceHelper;
private Handler mCameraThreadHandler;
private Surface mSurface;
private final float[] mTransformMatrix = new float[16];
private Paint mPaint = new Paint();
/**
* Constructor.
*
* @param uri uri of resource
* @param width width
* @param height height
*/
public VideoCapturerExternalResource(EglBase.Context sharedContext, final String uri, final int width, final int height) {
mUri = uri;
mWidth = width;
mHeight = height;
mFPS = 30;
HandlerThread cameraThread = new HandlerThread(TAG);
cameraThread.start();
mCameraThreadHandler = new Handler(cameraThread.getLooper());
mSurfaceHelper = SurfaceTextureHelper.create(sharedContext, mCameraThreadHandler);
}
@Override
public String getSupportedFormatsAsJson() throws JSONException {
JSONArray json_formats = new JSONArray();
JSONObject json_format = new JSONObject();
json_format.put("width", mWidth);
json_format.put("height", mHeight);
json_format.put("framerate", mFPS);
json_formats.put(json_format);
JSONObject json_format2 = new JSONObject();
json_format2.put("width", mHeight);
json_format2.put("height", mWidth);
json_format2.put("framerate", mFPS);
json_formats.put(json_format2);
if (DEBUG) {
Log.e(TAG, "@@@@ getSupportedFormatsAsJson: json=" + json_formats.toString(4));
}
return json_formats.toString();
}
@Override
public void startCapture(final int width, final int height, final int frameRate,
final Context applicationContext,
final VideoCapturerAndroid.CapturerObserver frameObserver) {
if (DEBUG) {
Log.i(TAG, "@@@ startCapture size:[" + width + ", " + height
+ "] frameRate:" + frameRate);
}
mSurfaceHelper.getSurfaceTexture().setDefaultBufferSize(width, height);
mSurface = new Surface(mSurfaceHelper.getSurfaceTexture());
mRequestWidth = width;
mRequestHeight = height;
mFrameObserver = frameObserver;
if (mClient != null) {
mClient.stop();
mClient = null;
}
mClient = new MixedReplaceMediaClient(mUri);
mClient.setOnMixedReplaceMediaListener(mOnMixedReplaceMediaListener);
mClient.start();
}
@Override
public void stopCapture() {
if (DEBUG) {
Log.i(TAG, "@@@ stopCapture");
}
if (mClient != null) {
mClient.stop();
mClient = null;
}
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
mFrameObserver = null;
}
@Override
public void returnBuffer(final long timeStamp) {
if (DEBUG) {
Log.d(TAG, "@@@ returnBuffer");
}
}
@Override
public void onOutputFormatRequest(final int width, final int height, final int fps) {
if (DEBUG) {
Log.d(TAG, "@@@ onOutputFormatRequest");
}
}
@Override
public void release() {
if (DEBUG) {
Log.d(TAG, "@@@ release");
}
stopCapture();
if (mSurfaceHelper != null) {
mSurfaceHelper.disconnect(mCameraThreadHandler);
mSurfaceHelper = null;
}
}
@Override
public void switchCamera(VideoCapturerAndroid.CameraSwitchHandler cameraSwitchHandler) {
if (DEBUG) {
Log.e(TAG, "switchCamera:");
}
}
@Override
public void changeCaptureFormat(int width, int height, int frameRate) {
if (DEBUG) {
Log.i(TAG, "changeCaptureFormat: (" + width + ", " + height + ") frameRate=" + frameRate);
}
}
@Override
public SurfaceTextureHelper getSurfaceTextureHelper() {
return mSurfaceHelper;
}
@Override
public void onTextureFrameAvailable(int oesTextureId, float[] transformMatrix, long timestampNs) {
if (mFrameObserver != null) {
mFrameObserver.onTextureFrameCaptured(mRequestWidth, mRequestHeight, oesTextureId, transformMatrix, 0, timestampNs);
}
}
private void deliverTextureFrame() {
mSurfaceHelper.getSurfaceTexture().getTransformMatrix(mTransformMatrix);
long timestampNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
onTextureFrameAvailable(mSurfaceHelper.getOesTextureId(), mTransformMatrix, timestampNs);
}
/**
* Receive an event from MixedReplaceMediaClient.
*/
private final MixedReplaceMediaClient.OnMixedReplaceMediaListener mOnMixedReplaceMediaListener
= new MixedReplaceMediaClient.OnMixedReplaceMediaListener() {
@Override
public void onConnected() {
mFrameObserver.onCapturerStarted(true);
}
@Override
public void onReceivedData(final InputStream in) {
final Bitmap bitmap = ImageUtils.resize(BitmapFactory.decodeStream(in));
if (bitmap != null) {
synchronized (mLockObj) {
if (mRequestWidth != bitmap.getWidth() || mRequestHeight != bitmap.getHeight()) {
if (DEBUG) {
Log.d(TAG, "@@@ ChangeSize: " + bitmap.getWidth() + " " + bitmap.getHeight());
}
mRequestWidth = bitmap.getWidth();
mRequestHeight = bitmap.getHeight();
mFrameObserver.onOutputFormatRequest(mRequestWidth, mRequestHeight, mFPS);
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
mSurfaceHelper.getSurfaceTexture().setDefaultBufferSize(mRequestWidth, mRequestHeight);
mSurface = new Surface(mSurfaceHelper.getSurfaceTexture());
}
Canvas canvas = mSurface.lockCanvas(null);
canvas.drawBitmap(bitmap, 0, 0, mPaint);
mSurface.unlockCanvasAndPost(canvas);
mCameraThreadHandler.post(new Runnable() {
@Override
public void run() {
mSurfaceHelper.getSurfaceTexture().updateTexImage();
deliverTextureFrame();
}
});
}
bitmap.recycle();
}
}
@Override
public void onError(MixedReplaceMediaClient.MixedReplaceMediaError error) {
if (DEBUG) {
Log.w(TAG, "Error occurred by MixedReplaceMediaClient. " + error);
}
if (mFrameObserver != null) {
mFrameObserver.onCapturerStarted(false);
}
}
};
}