Files
MoGoEagleEye/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/AbstractRendererHolder.java
donghongyu cdee266805 [Change]
开始USB摄像头的功能集成,还无法获取usb连接广播需要调试

Signed-off-by: donghongyu <donghongyu@zhidaoauto.com>
(cherry picked from commit b10306fc45)
2022-02-15 14:26:43 +08:00

1525 lines
41 KiB
Java

package com.serenegiant.glutils;
/*
* libcommon
* utility/helper classes for myself
*
* Copyright (c) 2014-2018 saki t_saki@serenegiant.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.opengl.Matrix;
import android.os.Build;
import android.util.Log;
import android.util.SparseArray;
import android.view.Surface;
import android.view.SurfaceHolder;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.serenegiant.utils.BuildCheck;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public abstract class AbstractRendererHolder implements IRendererHolder {
private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること
private static final String TAG = AbstractRendererHolder.class.getSimpleName();
private static final String RENDERER_THREAD_NAME = "RendererHolder";
private static final String CAPTURE_THREAD_NAME = "CaptureTask";
protected static final int REQUEST_DRAW = 1;
protected static final int REQUEST_UPDATE_SIZE = 2;
protected static final int REQUEST_ADD_SURFACE = 3;
protected static final int REQUEST_REMOVE_SURFACE = 4;
protected static final int REQUEST_REMOVE_SURFACE_ALL = 12;
protected static final int REQUEST_RECREATE_MASTER_SURFACE = 5;
protected static final int REQUEST_MIRROR = 6;
protected static final int REQUEST_ROTATE = 7;
protected static final int REQUEST_CLEAR = 8;
protected static final int REQUEST_CLEAR_ALL = 9;
protected static final int REQUEST_SET_MVP = 10;
protected final Object mSync = new Object();
@Nullable
private final RenderHolderCallback mCallback;
private volatile boolean isRunning;
private OutputStream mCaptureStream;
@StillCaptureFormat
private int mCaptureFormat;
@IntRange(from = 1L,to = 99L)
private int mCaptureCompression = DEFAULT_CAPTURE_COMPRESSION;
protected final RendererTask mRendererTask;
protected AbstractRendererHolder(final int width, final int height,
@Nullable final RenderHolderCallback callback) {
this(width, height,
3, null, EglTask.EGL_FLAG_RECORDABLE,
callback);
}
protected AbstractRendererHolder(final int width, final int height,
final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags,
@Nullable final RenderHolderCallback callback) {
mCallback = callback;
mRendererTask = createRendererTask(width, height,
maxClientVersion, sharedContext, flags);
new Thread(mRendererTask, RENDERER_THREAD_NAME).start();
if (!mRendererTask.waitReady()) {
// 初期化に失敗した時
throw new RuntimeException("failed to start renderer thread");
}
startCaptureTask();
}
//--------------------------------------------------------------------------------
// IRendererHolderの実装
@Override
public boolean isRunning() {
return isRunning;
}
/**
* 関係するすべてのリソースを開放する。再利用できない
*/
@Override
public void release() {
// if (DEBUG) Log.v(TAG, "release:");
mRendererTask.release();
synchronized (mSync) {
isRunning = false;
mSync.notifyAll();
}
// if (DEBUG) Log.v(TAG, "release:finished");
}
@Nullable
public EGLBase.IContext getContext() {
return mRendererTask.getContext();
}
/**
* マスター用の映像を受け取るためのSurfaceを取得
* @return
*/
@Override
public Surface getSurface() {
return mRendererTask.getSurface();
}
/**
* マスター用の映像を受け取るためのSurfaceTextureを取得
* @return
*/
@Override
public SurfaceTexture getSurfaceTexture() {
return mRendererTask.getSurfaceTexture();
}
/**
* マスター用の映像を受け取るためのマスターをチェックして無効なら再生成要求する
*/
@Override
public void reset() {
mRendererTask.checkMasterSurface();
}
/**
* マスター映像サイズをサイズ変更要求
* @param width
* @param height
*/
@Override
public void resize(final int width, final int height)
throws IllegalStateException {
mRendererTask.resize(width, height);
}
/**
* ミラーモードをセット
* @param mirror
*/
@Override
public void setMirror(@MirrorMode final int mirror) {
mRendererTask.mirror(mirror % MIRROR_NUM);
}
/**
* 現在のミラーモードを取得
* @return
*/
@Override
@MirrorMode
public int getMirror() {
return mRendererTask.mirror();
}
/**
* 分配描画用のSurfaceを追加
* このメソッドは指定したSurfaceが追加されるか
* interruptされるまでカレントスレッドをブロックする。
* @param id 普通はSurface#hashCodeを使う
* @param surface
* @param isRecordable
*/
@Override
public void addSurface(final int id,
final Object surface, final boolean isRecordable)
throws IllegalStateException, IllegalArgumentException {
// if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface);
mRendererTask.addSurface(id, surface);
}
/**
* 分配描画用のSurfaceを追加
* このメソッドは指定したSurfaceが追加されるか
* interruptされるまでカレントスレッドをブロックする。
* @param id 普通はSurface#hashCodeを使う
* @param surface
* @param isRecordable
* @param maxFps
*/
@Override
public void addSurface(final int id,
final Object surface, final boolean isRecordable, final int maxFps)
throws IllegalStateException, IllegalArgumentException {
// if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface);
mRendererTask.addSurface(id, surface, maxFps);
}
/**
* 分配描画用のSurfaceを削除要求する。
* このメソッドは指定したSurfaceが削除されるか
* interruptされるまでカレントスレッドをブロックする。
* @param id
*/
@Override
public void removeSurface(final int id) {
// if (DEBUG) Log.v(TAG, "removeSurface:id=" + id);
mRendererTask.removeSurface(id);
}
/**
* 分配描画用のSurfaceを全て削除要求する
* このメソッドはSurfaceが削除されるか
* interruptされるまでカレントスレッドをブロックする。
*/
@Override
public void removeSurfaceAll() {
// if (DEBUG) Log.v(TAG, "removeSurfaceAll:id=" + id);
mRendererTask.removeSurfaceAll();
}
/**
* 分配描画用のSurfaceを指定した色で塗りつぶす
* @param id
* @param color
*/
@Override
public void clearSurface(final int id, final int color) {
mRendererTask.clearSurface(id, color);
}
/**
* 分配描画用のSurfaceを指定した色で塗りつぶす
* @param color
*/
public void clearSurfaceAll(final int color) {
mRendererTask.clearSurfaceAll(color);
}
public void setMvpMatrix(final int id,
final int offset, @NonNull final float[] matrix) {
mRendererTask.setMvpMatrix(id, offset, matrix);
}
/**
* 分配描画用のSurfaceへの描画が有効かどうかを取得
* @param id
* @return
*/
@Override
public boolean isEnabled(final int id) {
return mRendererTask.isEnabled(id);
}
/**
* 分配描画用のSurfaceへの描画の有効・無効を切替
* @param id
* @param enable
*/
@Override
public void setEnabled(final int id, final boolean enable) {
mRendererTask.setEnabled(id, enable);
}
/**
* 強制的に現在の最新のフレームを描画要求する
* 分配描画用Surface全てが更新されるので注意
*/
@Override
public void requestFrame() {
mRendererTask.removeRequest(REQUEST_DRAW);
mRendererTask.offer(REQUEST_DRAW);
}
/**
* 追加されている分配描画用のSurfaceの数を取得
* @return
*/
@Override
public int getCount() {
return mRendererTask.getCount();
}
/**
* 静止画を撮影する
* 撮影完了を待機しない
* @param path
*/
@Deprecated
@Override
public void captureStillAsync(@NonNull final String path)
throws FileNotFoundException, IllegalStateException {
if (DEBUG) Log.v(TAG, "captureStillAsync:" + path);
captureStill(new BufferedOutputStream(new FileOutputStream(path)),
getCaptureFormat(path), DEFAULT_CAPTURE_COMPRESSION, false);
}
/**
* 静止画を撮影する
* 撮影完了を待機しない
* @param path
* @param captureCompression
*/
@Deprecated
@Override
public void captureStillAsync(@NonNull final String path,
@IntRange(from = 1L,to = 99L) final int captureCompression)
throws FileNotFoundException, IllegalStateException {
if (DEBUG) Log.v(TAG, "captureStillAsync:" + path
+ ",captureCompression=" + captureCompression);
captureStill(new BufferedOutputStream(new FileOutputStream(path)),
getCaptureFormat(path), captureCompression, false);
}
/**
* 静止画を撮影する
* 撮影完了を待機する
* @param path
*/
@Override
public void captureStill(@NonNull final String path)
throws FileNotFoundException, IllegalStateException {
if (DEBUG) Log.v(TAG, "captureStill:" + path);
captureStill(new BufferedOutputStream(new FileOutputStream(path)),
getCaptureFormat(path), DEFAULT_CAPTURE_COMPRESSION, true);
}
/**
* 静止画を撮影する
* 撮影完了を待機する
* @param path
*/
@Override
public void captureStill(@NonNull final String path,
@IntRange(from = 1L,to = 99L)final int captureCompression)
throws FileNotFoundException, IllegalStateException {
if (DEBUG) Log.v(TAG, "captureStill:" + path
+ ",captureCompression=" + captureCompression);
captureStill(new BufferedOutputStream(new FileOutputStream(path)),
getCaptureFormat(path), captureCompression, true);
}
/**
* 静止画を撮影する
* 撮影完了を待機する
* @param out
* @param captureFormat
* @param captureCompression
*/
@Override
public void captureStill(@NonNull final OutputStream out,
@StillCaptureFormat final int captureFormat,
@IntRange(from = 1L,to = 99L) final int captureCompression)
throws IllegalStateException {
captureStill(out, captureFormat, captureCompression, true);
}
/**
* 実際の静止画撮影要求メソッド
* @param out
* @param captureFormat
* @param captureCompression
* @param needWait 撮影完了を待機するを待機するかどうか
*/
private void captureStill(@NonNull final OutputStream out,
@StillCaptureFormat final int captureFormat,
@IntRange(from = 1L,to = 99L) final int captureCompression,
final boolean needWait) throws IllegalStateException {
synchronized (mSync) {
if (!isRunning) {
throw new IllegalStateException("already released?");
}
if (mCaptureStream != null) {
throw new IllegalStateException("already run still capturing now");
}
mCaptureStream = out;
mCaptureFormat = captureFormat;
mCaptureCompression = captureCompression;
mSync.notifyAll();
if (needWait) {
// 撮影完了街をする場合
for ( ; isRunning && (mCaptureStream != null) ; ) {
try {
if (DEBUG) Log.v(TAG, "静止画撮影待ち");
mSync.wait(1000);
} catch (final InterruptedException e) {
// ignore
}
}
}
}
if (DEBUG) Log.v(TAG, "captureStill:終了");
}
/**
* パス文字列の拡張子を調べて静止画圧縮フォーマットを取得する。
* jpeg(jpg)/png/webpのいずれでもなければIllegalArgumentExceptionを投げる
* @param path
* @return
* @throws IllegalArgumentException
*/
@StillCaptureFormat
private static int getCaptureFormat(@NonNull final String path)
throws IllegalArgumentException {
int result;
final String _path = path.toLowerCase();
if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
result = OUTPUT_FORMAT_JPEG;
} else if (path.endsWith(".png")) {
result = OUTPUT_FORMAT_PNG;
} else if (path.endsWith(".webp")) {
result = OUTPUT_FORMAT_WEBP;
} else {
throw new IllegalArgumentException("unknown compress format(extension)");
}
return result;
}
/**
* 静止画圧縮フォーマットをBitmap.CompressFormatに変換する
* @param captureFormat
* @return
*/
private static Bitmap.CompressFormat getCaptureFormat(
@StillCaptureFormat final int captureFormat) {
Bitmap.CompressFormat result;
switch (captureFormat) {
case OUTPUT_FORMAT_JPEG:
result = Bitmap.CompressFormat.JPEG;
break;
case OUTPUT_FORMAT_PNG:
result = Bitmap.CompressFormat.PNG;
break;
case OUTPUT_FORMAT_WEBP:
result = Bitmap.CompressFormat.WEBP;
break;
default:
result = Bitmap.CompressFormat.JPEG;
break;
}
return result;
}
//--------------------------------------------------------------------------------
@NonNull
protected abstract RendererTask createRendererTask(final int width, final int height,
final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags);
protected void startCaptureTask() {
new Thread(mCaptureTask, CAPTURE_THREAD_NAME).start();
synchronized (mSync) {
if (!isRunning) {
try {
mSync.wait();
} catch (final InterruptedException e) {
// ignore
}
}
}
}
protected void notifyCapture() {
// if (DEBUG) Log.v(TAG, "notifyCapture:");
synchronized (mCaptureTask) {
// キャプチャタスクに映像が更新されたことを通知
mCaptureTask.notify();
}
}
//--------------------------------------------------------------------------------
protected void callOnCreate(Surface surface) {
if (mCallback != null) {
try {
mCallback.onCreate(surface);
} catch (final Exception e) {
Log.w(TAG, e);
}
}
}
protected void callOnFrameAvailable() {
if (mCallback != null) {
try {
mCallback.onFrameAvailable();
} catch (final Exception e) {
Log.w(TAG, e);
}
}
}
protected void callOnDestroy() {
if (mCallback != null) {
try {
mCallback.onDestroy();
} catch (final Exception e) {
Log.w(TAG, e);
}
}
}
//--------------------------------------------------------------------------------
protected abstract static class BaseRendererTask extends EglTask {
private final SparseArray<RendererSurfaceRec> mClients
= new SparseArray<RendererSurfaceRec>();
private final AbstractRendererHolder mParent;
private int mVideoWidth, mVideoHeight;
final float[] mTexMatrix = new float[16];
int mTexId;
private SurfaceTexture mMasterTexture;
private Surface mMasterSurface;
@MirrorMode
private int mMirror = MIRROR_NORMAL;
private int mRotation = 0;
private volatile boolean mIsFirstFrameRendered;
public BaseRendererTask(@NonNull final AbstractRendererHolder parent,
final int width, final int height) {
this(parent, width, height,
3, null, EglTask.EGL_FLAG_RECORDABLE);
}
public BaseRendererTask(@NonNull final AbstractRendererHolder parent,
final int width, final int height,
final int maxClientVersion,
final EGLBase.IContext sharedContext, final int flags) {
super(maxClientVersion, sharedContext, flags);
mParent = parent;
mVideoWidth = width > 0 ? width : 640;
mVideoHeight = height > 0 ? height : 480;
}
/**
* ワーカースレッド開始時の処理(ここはワーカースレッド上)
*/
@Override
protected final void onStart() {
// if (DEBUG) Log.v(TAG, "onStart:");
handleReCreateMasterSurface();
internalOnStart();
synchronized (mParent.mSync) {
mParent.isRunning = true;
mParent.mSync.notifyAll();
}
// if (DEBUG) Log.v(TAG, "onStart:finished");
}
/**
* ワーカースレッド終了時の処理(ここはまだワーカースレッド上)
*/
@Override
protected void onStop() {
// if (DEBUG) Log.v(TAG, "onStop");
synchronized (mParent.mSync) {
mParent.isRunning = false;
mParent.mSync.notifyAll();
}
makeCurrent();
internalOnStop();
handleReleaseMasterSurface();
handleRemoveAll();
// if (DEBUG) Log.v(TAG, "onStop:finished");
}
@Override
protected boolean onError(final Exception e) {
// if (DEBUG) Log.w(TAG, e);
return false;
}
protected abstract void internalOnStart();
protected abstract void internalOnStop();
@Override
protected Object processRequest(final int request,
final int arg1, final int arg2, final Object obj) {
switch (request) {
case REQUEST_DRAW:
handleDraw();
break;
case REQUEST_UPDATE_SIZE:
handleResize(arg1, arg2);
break;
case REQUEST_ADD_SURFACE:
handleAddSurface(arg1, obj, arg2);
break;
case REQUEST_REMOVE_SURFACE:
handleRemoveSurface(arg1);
break;
case REQUEST_REMOVE_SURFACE_ALL:
handleRemoveAll();
break;
case REQUEST_RECREATE_MASTER_SURFACE:
handleReCreateMasterSurface();
break;
case REQUEST_MIRROR:
handleMirror(arg1);
break;
case REQUEST_ROTATE:
handleRotate(arg1, arg2);
break;
case REQUEST_CLEAR:
handleClear(arg1, arg2);
break;
case REQUEST_CLEAR_ALL:
handleClearAll(arg1);
break;
case REQUEST_SET_MVP:
handleSetMvp(arg1, arg2, obj);
break;
}
return null;
}
/**
* マスター映像取得用のSurfaceを取得
* @return
*/
public Surface getSurface() {
// if (DEBUG) Log.v(TAG, "getSurface:" + mMasterSurface);
checkMasterSurface();
return mMasterSurface;
}
/**
* マスター映像受け取り用のSurfaceTextureを取得
* @return
*/
public SurfaceTexture getSurfaceTexture() {
// if (DEBUG) Log.v(TAG, "getSurfaceTexture:" + mMasterTexture);
checkMasterSurface();
return mMasterTexture;
}
/**
* 分配描画用のSurfaceを追加
* このメソッドは指定したSurfaceが追加されるか
* interruptされるまでカレントスレッドをブロックする。
* @param id
* @param surface
*/
public void addSurface(final int id, final Object surface)
throws IllegalStateException, IllegalArgumentException {
addSurface(id, surface, -1);
}
/**
* 分配描画用のSurfaceを追加
* このメソッドは指定したSurfaceが追加されるか
* interruptされるまでカレントスレッドをブロックする。
* @param id
* @param surface
*/
public void addSurface(final int id,
final Object surface, final int maxFps)
throws IllegalStateException, IllegalArgumentException {
checkFinished();
if (!((surface instanceof SurfaceTexture)
|| (surface instanceof Surface)
|| (surface instanceof SurfaceHolder))) {
throw new IllegalArgumentException(
"Surface should be one of Surface, SurfaceTexture or SurfaceHolder");
}
synchronized (mClients) {
if (mClients.get(id) == null) {
for ( ; isRunning() ; ) {
if (offer(REQUEST_ADD_SURFACE, id, maxFps, surface)) {
try {
mClients.wait();
} catch (final InterruptedException e) {
// ignore
}
break;
} else {
// キューに追加できなかった時は待機する
try {
mClients.wait(5);
} catch (final InterruptedException e) {
break;
}
}
}
}
}
}
/**
* 分配描画用のSurfaceを削除
* このメソッドは指定したSurfaceが削除されるか
* interruptされるまでカレントスレッドをブロックする。
* @param id
*/
public void removeSurface(final int id) {
synchronized (mClients) {
if (mClients.get(id) != null) {
for ( ; isRunning() ; ) {
if (offer(REQUEST_REMOVE_SURFACE, id)) {
try {
mClients.wait();
} catch (final InterruptedException e) {
// ignore
}
break;
} else {
// キューに追加できなかった時は待機する
try {
mClients.wait(5);
} catch (final InterruptedException e) {
break;
}
}
}
}
}
}
/**
* 分配描画用のSurfaceを全て削除する
* このメソッドはSurfaceが削除されるか
* interruptされるまでカレントスレッドをブロックする。
*/
public void removeSurfaceAll() {
synchronized (mClients) {
for ( ; isRunning() ; ) {
if (offer(REQUEST_REMOVE_SURFACE_ALL)) {
try {
mClients.wait();
} catch (final InterruptedException e) {
// ignore
}
break;
} else {
// キューに追加できなかった時は待機する
try {
mClients.wait(5);
} catch (final InterruptedException e) {
break;
}
}
}
}
}
/**
* 指定したIDの分配描画用のSurfaceを指定した色で塗りつぶす
* @param id
* @param color
*/
public void clearSurface(final int id, final int color) {
checkFinished();
offer(REQUEST_CLEAR, id, color);
}
public void clearSurfaceAll(final int color) {
checkFinished();
offer(REQUEST_CLEAR_ALL, color);
}
public void setMvpMatrix(final int id,
final int offset, @NonNull final float[] matrix) {
checkFinished();
offer(REQUEST_SET_MVP, id, offset, matrix);
}
public boolean isEnabled(final int id) {
synchronized (mClients) {
final RendererSurfaceRec rec = mClients.get(id);
return rec != null && rec.isEnabled();
}
}
public void setEnabled(final int id, final boolean enable) {
synchronized (mClients) {
final RendererSurfaceRec rec = mClients.get(id);
if (rec != null) {
rec.setEnabled(enable);
}
}
}
/**
* 分配描画用のSurfaceの数を取得
* @return
*/
public int getCount() {
synchronized (mClients) {
return mClients.size();
}
}
/**
* リサイズ
* @param width
* @param height
*/
public void resize(final int width, final int height)
throws IllegalStateException {
checkFinished();
if ( ((width > 0) && (height > 0))
&& ((mVideoWidth != width) || (mVideoHeight != height)) ) {
offer(REQUEST_UPDATE_SIZE, width, height);
}
}
protected int width() {
return mVideoWidth;
}
protected int height() {
return mVideoHeight;
}
public void mirror(final int mirror) {
checkFinished();
if (mMirror != mirror) {
offer(REQUEST_MIRROR, mirror);
}
}
@MirrorMode
public int mirror() {
return mMirror;
}
/**
* 分配描画用のマスターSurfaceが有効かどうかをチェックして無効なら再生成する
*/
public void checkMasterSurface() {
checkFinished();
if ((mMasterSurface == null) || (!mMasterSurface.isValid())) {
Log.d(TAG, "checkMasterSurface:invalid master surface");
offerAndWait(REQUEST_RECREATE_MASTER_SURFACE, 0, 0, null);
}
}
protected void checkFinished() throws IllegalStateException {
if (isFinished()) {
throw new IllegalStateException("already finished");
}
}
protected AbstractRendererHolder getParent() {
return mParent;
}
//================================================================================
// ワーカースレッド上での処理
//================================================================================
/**
* 実際の描画処理
*/
protected void handleDraw() {
if ((mMasterSurface == null) || (!mMasterSurface.isValid())) {
Log.e(TAG, "checkMasterSurface:invalid master surface");
offer(REQUEST_RECREATE_MASTER_SURFACE);
return;
}
if (mIsFirstFrameRendered) {
try {
makeCurrent();
handleUpdateTexture();
} catch (final Exception e) {
Log.e(TAG, "draw:thread id =" + Thread.currentThread().getId(), e);
offer(REQUEST_RECREATE_MASTER_SURFACE);
return;
}
mParent.notifyCapture();
preprocess();
handleDrawClients();
mParent.callOnFrameAvailable();
}
makeCurrent();
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glFlush();
}
protected void handleUpdateTexture() {
mMasterTexture.updateTexImage();
mMasterTexture.getTransformMatrix(mTexMatrix);
}
protected abstract void preprocess();
/**
* 各Surfaceへ描画する
*/
protected void handleDrawClients() {
synchronized (mClients) {
final int n = mClients.size();
RendererSurfaceRec client;
for (int i = n - 1; i >= 0; i--) {
client = mClients.valueAt(i);
if ((client != null) && client.canDraw()) {
try {
onDrawClient(client, mTexId, mTexMatrix);
} catch (final Exception e) {
// removeSurfaceが呼ばれなかったかremoveSurfaceを呼ぶ前に破棄されてしまった
mClients.removeAt(i);
client.release();
}
}
}
}
}
/**
* Surface1つの描画処理
* @param client
* @param texId
* @param texMatrix
*/
protected abstract void onDrawClient(
@NonNull final RendererSurfaceRec client,
final int texId, final float[] texMatrix);
/**
* 指定したIDの分配描画先Surfaceを追加する
* @param id
* @param surface
*/
protected void handleAddSurface(final int id,
final Object surface, final int maxFps) {
// if (DEBUG) Log.v(TAG, "handleAddSurface:id=" + id);
checkSurface();
synchronized (mClients) {
RendererSurfaceRec client = mClients.get(id);
if (client == null) {
try {
client = RendererSurfaceRec.newInstance(getEgl(), surface, maxFps);
setMirror(client, mMirror);
mClients.append(id, client);
} catch (final Exception e) {
Log.w(TAG, "invalid surface: surface=" + surface, e);
}
} else {
Log.w(TAG, "surface is already added: id=" + id);
}
mClients.notifyAll();
}
}
/**
* 指定したIDの分配描画先Surfaceを破棄する
* @param id
*/
protected void handleRemoveSurface(final int id) {
// if (DEBUG) Log.v(TAG, "handleRemoveSurface:id=" + id);
synchronized (mClients) {
final RendererSurfaceRec client = mClients.get(id);
if (client != null) {
mClients.remove(id);
if (client.isValid()) {
client.clear(0); // XXX 黒で塗りつぶし, 色指定できるようにする?
}
client.release();
}
checkSurface();
mClients.notifyAll();
}
}
/**
* 念の為に分配描画先のSurfaceを全て破棄する
*/
protected void handleRemoveAll() {
// if (DEBUG) Log.v(TAG, "handleRemoveAll:");
synchronized (mClients) {
final int n = mClients.size();
RendererSurfaceRec client;
for (int i = 0; i < n; i++) {
client = mClients.valueAt(i);
if (client != null) {
if (client.isValid()) {
client.clear(0); // XXX 黒で塗りつぶし, 色指定できるようにする?
}
client.release();
}
}
mClients.clear();
mClients.notifyAll();
}
// if (DEBUG) Log.v(TAG, "handleRemoveAll:finished");
}
/**
* 分配描画先のSurfaceが有効かどうかをチェックして無効なものは削除する
*/
protected void checkSurface() {
// if (DEBUG) Log.v(TAG, "checkSurface");
synchronized (mClients) {
final int n = mClients.size();
for (int i = 0; i < n; i++) {
final RendererSurfaceRec client = mClients.valueAt(i);
if ((client != null) && !client.isValid()) {
final int id = mClients.keyAt(i);
// if (DEBUG) Log.i(TAG, "checkSurface:found invalid surface:id=" + id);
mClients.valueAt(i).release();
mClients.remove(id);
}
}
}
// if (DEBUG) Log.v(TAG, "checkSurface:finished");
}
/**
* 指定したIDの分配描画用Surfaceを指定した色で塗りつぶす
* @param id
* @param color
*/
protected void handleClear(final int id, final int color) {
synchronized (mClients) {
final RendererSurfaceRec client = mClients.get(id);
if ((client != null) && client.isValid()) {
client.clear(color);
}
}
}
/**
* 分配描画用Surface全てを指定した色で塗りつぶす
* @param color
*/
protected void handleClearAll(final int color) {
synchronized (mClients) {
final int n = mClients.size();
for (int i = 0; i < n; i++) {
final RendererSurfaceRec client = mClients.valueAt(i);
if ((client != null) && client.isValid()) {
client.clear(color);
}
}
}
}
/**
* モデルビュー変換行列を適用する
* @param id
* @param offset
* @param mvp
*/
protected void handleSetMvp(final int id,
final int offset, final Object mvp) {
if ((mvp instanceof float[]) && (((float[]) mvp).length >= 16 + offset)) {
final float[] array = (float[])mvp;
synchronized (mClients) {
final RendererSurfaceRec client = mClients.get(id);
if ((client != null) && client.isValid()) {
System.arraycopy(array, offset, client.mMvpMatrix, 0, 16);
}
}
}
}
/**
* マスターSurfaceを再生成する
*/
@SuppressLint("NewApi")
protected void handleReCreateMasterSurface() {
makeCurrent();
handleReleaseMasterSurface();
makeCurrent();
mTexId = GLHelper.initTex(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_NEAREST);
mMasterTexture = new SurfaceTexture(mTexId);
mMasterSurface = new Surface(mMasterTexture);
if (BuildCheck.isAndroid4_1()) {
mMasterTexture.setDefaultBufferSize(mVideoWidth, mVideoHeight);
}
mMasterTexture.setOnFrameAvailableListener(mOnFrameAvailableListener);
mParent.callOnCreate(mMasterSurface);
}
/**
* マスターSurfaceを破棄する
*/
protected void handleReleaseMasterSurface() {
if (mMasterSurface != null) {
try {
mMasterSurface.release();
} catch (final Exception e) {
Log.w(TAG, e);
}
mMasterSurface = null;
mParent.callOnDestroy();
}
if (mMasterTexture != null) {
try {
mMasterTexture.release();
} catch (final Exception e) {
Log.w(TAG, e);
}
mMasterTexture = null;
}
if (mTexId != 0) {
GLHelper.deleteTex(mTexId);
mTexId = 0;
}
}
/**
* マスター映像サイズをリサイズ
* @param width
* @param height
*/
@SuppressLint("NewApi")
protected void handleResize(final int width, final int height) {
// if (DEBUG) Log.v(TAG, String.format("handleResize:(%d,%d)", width, height));
mVideoWidth = width;
mVideoHeight = height;
if (BuildCheck.isAndroid4_1()) {
mMasterTexture.setDefaultBufferSize(mVideoWidth, mVideoHeight);
}
}
/**
* ミラーモードをセット
* @param mirror
*/
protected void handleMirror(final int mirror) {
mMirror = mirror;
synchronized (mClients) {
final int n = mClients.size();
for (int i = 0; i < n; i++) {
final RendererSurfaceRec client = mClients.valueAt(i);
if (client != null) {
setMirror(client, mirror);
}
}
}
}
/**
* handleMirrorの下請け
* @param client
* @param mirror
*/
protected void setMirror(final RendererSurfaceRec client, final int mirror) {
RendererHolder.setMirror(client.mMvpMatrix, mirror);
}
protected void handleRotate(final int id, final int degree) {
// FIXME 未実装
}
/**
* TextureSurfaceで映像を受け取った際のコールバックリスナー
*/
protected final SurfaceTexture.OnFrameAvailableListener
mOnFrameAvailableListener = new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(final SurfaceTexture surfaceTexture) {
removeRequest(REQUEST_DRAW);
mIsFirstFrameRendered = true;
offer(REQUEST_DRAW);
}
};
}
/**
* ワーカースレッド上でOpenGL|ESを用いてマスター映像を分配描画するためのインナークラス
*/
protected abstract static class RendererTask extends BaseRendererTask {
protected GLDrawer2D mDrawer;
public RendererTask(@NonNull final AbstractRendererHolder parent,
final int width, final int height) {
super(parent, width, height);
}
public RendererTask(@NonNull final AbstractRendererHolder parent,
final int width, final int height,
final int maxClientVersion,
final EGLBase.IContext sharedContext, final int flags) {
super(parent, width, height, maxClientVersion, sharedContext, flags);
}
@Override
protected void internalOnStart() {
mDrawer = new GLDrawer2D(true);
}
@Override
protected void internalOnStop() {
if (mDrawer != null) {
mDrawer.release();
mDrawer = null;
}
}
@Override
protected void preprocess() {
}
@Override
protected void onDrawClient(@NonNull final RendererSurfaceRec client,
final int texId, final float[] texMatrix) {
client.draw(mDrawer, texId, texMatrix);
}
}
//--------------------------------------------------------------------------------
protected void setupCaptureDrawer(final GLDrawer2D drawer) {
}
/**
* 静止画を非同期でキャプチャするためのRunnable
*/
private final Runnable mCaptureTask = new Runnable() {
EGLBase eglBase;
EGLBase.IEglSurface captureSurface;
GLDrawer2D drawer;
final float[] mMvpMatrix = new float[16];
@Override
public void run() {
// if (DEBUG) Log.v(TAG, "captureTask start");
synchronized (mSync) {
// 描画スレッドが実行されるまで待機
for (; !isRunning && !mRendererTask.isFinished(); ) {
try {
mSync.wait(1000);
} catch (final InterruptedException e) {
break;
}
}
}
if (isRunning) {
init();
try {
if ((eglBase.getGlVersion() > 2) && (BuildCheck.isAndroid4_3())) {
captureLoopGLES3();
} else {
captureLoopGLES2();
}
} catch (final Exception e) {
Log.w(TAG, e);
} finally {
// release resources
release();
}
}
// if (DEBUG) Log.v(TAG, "captureTask finished");
}
private final void init() {
eglBase = EGLBase.createFrom(3,
mRendererTask.getContext(), false, 0, false);
captureSurface = eglBase.createOffscreen(
mRendererTask.width(), mRendererTask.height());
Matrix.setIdentityM(mMvpMatrix, 0);
drawer = new GLDrawer2D(true);
setupCaptureDrawer(drawer);
}
private final void captureLoopGLES2() {
int width = -1, height = -1;
ByteBuffer buf = null;
int captureCompression = DEFAULT_CAPTURE_COMPRESSION;
// if (DEBUG) Log.v(TAG, "captureTask loop");
for (; isRunning ;) {
synchronized (mSync) {
if (mCaptureStream == null) {
try {
mSync.wait();
} catch (final InterruptedException e) {
break;
}
if (mCaptureStream != null) {
// if (DEBUG) Log.i(TAG, "静止画撮影要求を受け取った");
captureCompression = mCaptureCompression;
if ((captureCompression <= 0) || (captureCompression >= 100)) {
captureCompression = 90;
}
} else {
// 起床されたけどmCaptureStreamがnullだった
continue;
}
}
if (DEBUG) Log.v(TAG, "#captureLoopGLES2:start capture");
if ((buf == null)
|| (width != mRendererTask.width())
|| (height != mRendererTask.height())) {
width = mRendererTask.width();
height = mRendererTask.height();
buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
if (captureSurface != null) {
captureSurface.release();
captureSurface = null;
}
captureSurface = eglBase.createOffscreen(width, height);
}
if (isRunning && (width > 0) && (height > 0)) {
setMirror(mMvpMatrix, mRendererTask.mirror());
mMvpMatrix[5] *= -1.0f; // flip up-side down
drawer.setMvpMatrix(mMvpMatrix, 0);
captureSurface.makeCurrent();
drawer.draw(mRendererTask.mTexId, mRendererTask.mTexMatrix, 0);
captureSurface.swap();
buf.clear();
GLES20.glReadPixels(0, 0, width, height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
// if (DEBUG) Log.v(TAG, "save pixels to file:" + captureFile);
final Bitmap.CompressFormat compressFormat
= getCaptureFormat(mCaptureFormat);
try {
try {
final Bitmap bmp = Bitmap.createBitmap(
width, height, Bitmap.Config.ARGB_8888);
buf.clear();
bmp.copyPixelsFromBuffer(buf);
bmp.compress(compressFormat, captureCompression, mCaptureStream);
bmp.recycle();
mCaptureStream.flush();
} finally {
mCaptureStream.close();
}
} catch (final IOException e) {
Log.w(TAG, "failed to save file", e);
}
} else if (isRunning) {
Log.w(TAG, "#captureLoopGLES3:unexpectedly width/height is zero");
}
if (DEBUG) Log.i(TAG, "#captureLoopGLES2:静止画撮影終了");
mCaptureStream = null;
mSync.notifyAll();
} // end of synchronized (mSync)
} // end of for (; isRunning ;)
synchronized (mSync) {
mSync.notifyAll();
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private final void captureLoopGLES3() {
int width = -1, height = -1;
ByteBuffer buf = null;
int captureCompression = 90;
// if (DEBUG) Log.v(TAG, "captureTask loop");
for (; isRunning ;) {
synchronized (mSync) {
if (mCaptureStream == null) {
try {
mSync.wait();
} catch (final InterruptedException e) {
break;
}
if (mCaptureStream != null) {
// if (DEBUG) Log.i(TAG, "静止画撮影要求を受け取った");
captureCompression = mCaptureCompression;
if ((captureCompression <= 0) || (captureCompression >= 100)) {
captureCompression = 90;
}
} else {
// 起床されたけどmCaptureStreamがnullだった
continue;
}
}
if (DEBUG) Log.v(TAG, "#captureLoopGLES3:start capture");
if ((buf == null)
|| (width != mRendererTask.width())
|| (height != mRendererTask.height())) {
width = mRendererTask.width();
height = mRendererTask.height();
buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
if (captureSurface != null) {
captureSurface.release();
captureSurface = null;
}
captureSurface = eglBase.createOffscreen(width, height);
}
if (isRunning && (width > 0) && (height > 0)) {
setMirror(mMvpMatrix, mRendererTask.mirror());
mMvpMatrix[5] *= -1.0f; // flip up-side down
drawer.setMvpMatrix(mMvpMatrix, 0);
captureSurface.makeCurrent();
drawer.draw(mRendererTask.mTexId, mRendererTask.mTexMatrix, 0);
captureSurface.swap();
buf.clear();
// FIXME これはGL|ES3のPBOとglMapBufferRange/glUnmapBufferを使うように変更する
GLES30.glReadPixels(0, 0, width, height,
GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, buf);
// if (DEBUG) Log.v(TAG, "save pixels to file:" + captureFile);
final Bitmap.CompressFormat compressFormat
= getCaptureFormat(mCaptureFormat);
try {
try {
final Bitmap bmp = Bitmap.createBitmap(
width, height, Bitmap.Config.ARGB_8888);
buf.clear();
bmp.copyPixelsFromBuffer(buf);
bmp.compress(compressFormat, captureCompression, mCaptureStream);
bmp.recycle();
mCaptureStream.flush();
} finally {
mCaptureStream.close();
}
} catch (final IOException e) {
Log.w(TAG, "failed to save file", e);
}
} else if (isRunning) {
Log.w(TAG, "#captureLoopGLES3:unexpectedly width/height is zero");
}
if (DEBUG) Log.i(TAG, "#captureLoopGLES3:静止画撮影終了");
mCaptureStream = null;
mSync.notifyAll();
} // end of synchronized (mSync)
} // end of for (; isRunning ;)
synchronized (mSync) {
mSync.notifyAll();
}
}
private final void release() {
if (captureSurface != null) {
captureSurface.makeCurrent();
captureSurface.release();
captureSurface = null;
}
if (drawer != null) {
drawer.release();
drawer = null;
}
if (eglBase != null) {
eglBase.release();
eglBase = null;
}
}
};
//================================================================================
protected static void setMirror(final float[] mvp, final int mirror) {
switch (mirror) {
case MIRROR_NORMAL:
mvp[0] = Math.abs(mvp[0]);
mvp[5] = Math.abs(mvp[5]);
break;
case MIRROR_HORIZONTAL:
mvp[0] = -Math.abs(mvp[0]); // flip left-right
mvp[5] = Math.abs(mvp[5]);
break;
case MIRROR_VERTICAL:
mvp[0] = Math.abs(mvp[0]);
mvp[5] = -Math.abs(mvp[5]); // flip up-side down
break;
case MIRROR_BOTH:
mvp[0] = -Math.abs(mvp[0]); // flip left-right
mvp[5] = -Math.abs(mvp[5]); // flip up-side down
break;
}
}
/**
* 現在のモデルビュー変換行列をxy平面で指定した角度回転させる
* @param mvp
* @param degrees
*/
protected static void rotate(final float[] mvp, final int degrees) {
if ((degrees % 180) != 0) {
Matrix.rotateM(mvp, 0, degrees, 0.0f, 0.0f, 1.0f);
}
}
/**
* モデルビュー変換行列にxy平面で指定した角度回転させた回転行列をセットする
* @param mvp
* @param degrees
*/
protected static void setRotation(final float[] mvp, final int degrees) {
Matrix.setIdentityM(mvp, 0);
if ((degrees % 180) != 0) {
Matrix.rotateM(mvp, 0, degrees, 0.0f, 0.0f, 1.0f);
}
}
}