diff --git a/core/function-impl/mogo-core-function-hmi/build.gradle b/core/function-impl/mogo-core-function-hmi/build.gradle index 6ba321c28a..56eebd3443 100644 --- a/core/function-impl/mogo-core-function-hmi/build.gradle +++ b/core/function-impl/mogo-core-function-hmi/build.gradle @@ -57,16 +57,17 @@ dependencies { if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { implementation rootProject.ext.dependencies.androidxrecyclerview implementation rootProject.ext.dependencies.modulecommon + implementation project(':libraries:map-usbcamera') implementation rootProject.ext.dependencies.mogo_core_res implementation rootProject.ext.dependencies.mogo_core_data implementation rootProject.ext.dependencies.mogo_core_utils implementation rootProject.ext.dependencies.mogo_core_network implementation rootProject.ext.dependencies.mogo_core_function_call -// implementation rootProject.ext.dependencies.mogo_core_res } else { implementation project(':modules:mogo-module-common') implementation project(':services:mogo-service-api') + implementation project(':libraries:map-usbcamera') implementation project(':core:mogo-core-res') implementation project(':core:mogo-core-data') @@ -74,7 +75,6 @@ dependencies { implementation project(':core:mogo-core-res') implementation project(':core:mogo-core-function-api') implementation project(':core:mogo-core-function-call') -// implementation project(':core:mogo-core-res') } } diff --git a/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml index 4e3fdff847..908d7fa563 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml +++ b/core/function-impl/mogo-core-function-hmi/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt index fb28a0da14..e3161d576c 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/MoGoHmiFragment.kt @@ -26,6 +26,7 @@ import com.mogo.eagle.core.function.hmi.notification.WarningFloat import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern import com.mogo.eagle.core.function.hmi.ui.camera.CameraListView +import com.mogo.eagle.core.function.hmi.ui.carcorder.CarcorderPreviewView import com.mogo.eagle.core.function.hmi.ui.notice.NoticeBannerView import com.mogo.eagle.core.function.hmi.ui.notice.NoticeNormalBannerView import com.mogo.eagle.core.function.hmi.ui.setting.DebugSettingView @@ -86,6 +87,11 @@ class MoGoHmiFragment : MvpFragment } } + ivCameraIcon?.setOnLongClickListener { + activity?.let { it1 -> CarcorderPreviewView.show(it1) } + true + } + ivToolsIcon?.setOnClickListener { if (toolsViewFloat == null) { showToolsFloat() diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt new file mode 100644 index 0000000000..cb7b0b955d --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/carcorder/CarcorderPreviewView.kt @@ -0,0 +1,215 @@ +package com.mogo.eagle.core.function.hmi.ui.carcorder + +import android.animation.Animator +import android.app.Activity +import android.content.Context +import android.hardware.usb.UsbDevice +import android.os.Looper +import android.util.Log +import android.view.* +import android.view.animation.OvershootInterpolator +import android.widget.Toast +import com.mogo.eagle.core.function.hmi.R +import com.mogo.eagle.core.function.hmi.notification.WarningFloat +import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator +import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern +import com.mogo.eagle.core.utilcode.mogo.logger.Logger +import com.mogo.usbcamera.UVCCameraHelper +import com.serenegiant.usb.ParentPreviewConstraintLayout +import com.serenegiant.usb.widget.CameraViewInterface +import kotlinx.android.synthetic.main.view_carcorder_preview.view.* + +/** + * @author donghongyu + * @date 2021/9/30 8:46 下午 + * USB-Camera 摄像头预览 + * + * TODO 临时方案,后面考虑封装将摄像头数据流通过 Tensorflow-lite 处理后展示 + */ +class CarcorderPreviewView private constructor( + context: Context +) : ParentPreviewConstraintLayout(context), + CameraViewInterface.Callback { + + private val TAG = "CarcorderPreviewView" + + + private var mCameraHelper: UVCCameraHelper? = null + + private var isRequest = false + private var isPreview = false + + init { + LayoutInflater.from(context).inflate(R.layout.view_carcorder_preview, this, true) + initView() + } + + companion object { + private var mCarcorderPreviewViewFloat: WarningFloat.Builder? = null + + @Volatile + var instance: CarcorderPreviewView? = null + + fun getInstance(context: Activity): CarcorderPreviewView { + if (instance == null) { + synchronized(CarcorderPreviewView::class) { + if (instance == null) { + instance = CarcorderPreviewView(context) + } + } + } + return instance!! + } + + /** + * 展示窗口 + */ + fun show(context: Activity) { + if (mCarcorderPreviewViewFloat == null) { + val carcorderPreviewVie = getInstance(context) + mCarcorderPreviewViewFloat = WarningFloat.with(context) + .setTag("CarcorderPreviewView") + .setLayout(carcorderPreviewVie) + .setSidePattern(SidePattern.RIGHT) + .setGravity(Gravity.RIGHT, offsetY = 200) + .setImmersionStatusBar(true) + .setAnimator(object : DefaultAnimator() { + override fun enterAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.enterAnim(view, params, windowManager, sidePattern) + ?.apply { + interpolator = OvershootInterpolator() + } + + override fun exitAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = + super.exitAnim(view, params, windowManager, sidePattern) + ?.setDuration(200) + }) + .show() + } else { + dismiss() + } + } + + /** + * 隐藏窗口 + */ + fun dismiss() { + if (mCarcorderPreviewViewFloat != null) { + WarningFloat.dismiss(mCarcorderPreviewViewFloat!!.config.floatTag, false) + mCarcorderPreviewViewFloat = null + } + } + } + + private val listener: UVCCameraHelper.OnMyDevConnectListener = object : UVCCameraHelper.OnMyDevConnectListener { + override fun onAttachDev(device: UsbDevice?) { + // request open permission + if (!isRequest) { + isRequest = true + mCameraHelper?.requestPermission(0) + } + } + + override fun onDettachDev(device: UsbDevice) { + // close camera + if (isRequest) { + isRequest = false + mCameraHelper?.closeCamera() + showShortMsg(device.deviceName + " is out") + } + } + + override fun onConnectDev(device: UsbDevice?, isConnected: Boolean) { + if (!isConnected) { + showShortMsg("fail to connect,please check resolution params") + isPreview = false + } else { + isPreview = true + showShortMsg("相机连接中") + // initialize seekbar + // need to wait UVCCamera initialize over + Thread { + try { + Thread.sleep(2500) + } catch (e: InterruptedException) { + e.printStackTrace() + } + Looper.prepare() + if (mCameraHelper != null && mCameraHelper!!.isCameraOpened) { + Logger.d(TAG, "亮度(brightness):${mCameraHelper!!.getModelValue(UVCCameraHelper.MODE_BRIGHTNESS)}") + Logger.d(TAG, "对比度(contrast):${mCameraHelper!!.getModelValue(UVCCameraHelper.MODE_CONTRAST)}") + } + Looper.loop() + }.start() + } + } + + override fun onDisConnectDev(device: UsbDevice?) { + showShortMsg("相机断开连接") + } + } + + + private fun showShortMsg(msg: String) { + Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() + } + + private fun initView() { + // step.1 initialize UVCCameraHelper + carcorderPreview.setCallback(this) + mCameraHelper = UVCCameraHelper.getInstance() + mCameraHelper?.setDefaultFrameFormat(UVCCameraHelper.FRAME_FORMAT_MJPEG) + mCameraHelper?.initUSBMonitor(context as Activity, carcorderPreview, listener) + + mCameraHelper?.setOnPreviewFrameListener { nv21Yuv -> Log.d(TAG, "onPreviewResult: " + nv21Yuv.size) } + } + + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + // step.2 register USB event broadcast + if (mCameraHelper != null) { + mCameraHelper!!.registerUSB() + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + // step.3 unregister USB event broadcast + if (mCameraHelper != null) { + mCameraHelper!!.unregisterUSB() + } + + + } + + override fun onSurfaceCreated(view: CameraViewInterface?, surface: Surface?) { + if (!isPreview && mCameraHelper!!.isCameraOpened) { + mCameraHelper!!.startPreview(carcorderPreview) + isPreview = true + } + } + + override fun onSurfaceChanged(view: CameraViewInterface?, surface: Surface?, width: Int, height: Int) { + + } + + override fun onSurfaceDestroy(view: CameraViewInterface?, surface: Surface?) { + if (isPreview && mCameraHelper!!.isCameraOpened) { + mCameraHelper!!.stopPreview() + isPreview = false + } + } + + +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_carcorder_preview.xml b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_carcorder_preview.xml new file mode 100644 index 0000000000..90e193ede0 --- /dev/null +++ b/core/function-impl/mogo-core-function-hmi/src/main/res/layout/view_carcorder_preview.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/libraries/map-usbcamera/build.gradle b/libraries/map-usbcamera/build.gradle index 776c9580e3..77459969fe 100644 --- a/libraries/map-usbcamera/build.gradle +++ b/libraries/map-usbcamera/build.gradle @@ -59,9 +59,10 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation rootProject.ext.dependencies.androidxconstraintlayout implementation rootProject.ext.dependencies.arouter kapt rootProject.ext.dependencies.aroutercompiler - implementation(name: 'common-4.1.1', ext: 'aar') + //implementation(name: 'common-4.1.1', ext: 'aar') if (Boolean.valueOf(USE_MAVEN_PACKAGE)) { diff --git a/libraries/map-usbcamera/src/main/AndroidManifest.xml b/libraries/map-usbcamera/src/main/AndroidManifest.xml index 6173006460..9f80e8f54e 100644 --- a/libraries/map-usbcamera/src/main/AndroidManifest.xml +++ b/libraries/map-usbcamera/src/main/AndroidManifest.xml @@ -1,6 +1,9 @@ + + + diff --git a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java index 76fb1d5da2..da4d005ea8 100644 --- a/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java +++ b/libraries/map-usbcamera/src/main/java/com/mogo/usbcamera/UVCCameraHelper.java @@ -5,7 +5,6 @@ import android.graphics.SurfaceTexture; import android.hardware.usb.UsbDevice; import android.os.Environment; -import org.easydarwin.sw.TxtOverlay; import com.serenegiant.usb.DeviceFilter; import com.serenegiant.usb.Size; import com.serenegiant.usb.USBMonitor; @@ -15,13 +14,15 @@ import com.serenegiant.usb.common.UVCCameraHandler; import com.serenegiant.usb.encoder.RecordParams; import com.serenegiant.usb.widget.CameraViewInterface; +import org.easydarwin.sw.TxtOverlay; import java.io.File; import java.util.List; import java.util.Objects; -/** UVCCamera Helper class - * +/** + * UVCCamera Helper class + *

* Created by jiangdongguo on 2017/9/30. */ @@ -121,8 +122,8 @@ public class UVCCameraHelper { startPreview(mCamView); } }).start(); - if(listener != null) { - listener.onConnectDev(device,true); + if (listener != null) { + listener.onConnectDev(device, true); } } @@ -154,7 +155,7 @@ public class UVCCameraHelper { mCameraHandler = null; } // initialize camera handler - mCamView.setAspectRatio(previewWidth / (float)previewHeight); + mCamView.setAspectRatio(previewWidth / (float) previewHeight); mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2, previewWidth, previewHeight, mFrameFormat); } @@ -169,8 +170,8 @@ public class UVCCameraHelper { mCameraHandler.release(); mCameraHandler = null; } - mCamView.setAspectRatio(previewWidth / (float)previewHeight); - mCameraHandler = UVCCameraHandler.createHandler(mActivity,mCamView, 2, + mCamView.setAspectRatio(previewWidth / (float) previewHeight); + mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2, previewWidth, previewHeight, mFrameFormat); openCamera(mCtrlBlock); new Thread(new Runnable() { @@ -222,8 +223,9 @@ public class UVCCameraHelper { return; } int count = devList.size(); - if (index >= count) + if (index >= count) { new IllegalArgumentException("index illegal,should be < devList.size()"); + } if (mUSBMonitor != null) { mUSBMonitor.requestPermission(getUsbDeviceList().get(index)); } @@ -240,21 +242,22 @@ public class UVCCameraHelper { public List getUsbDeviceList() { List deviceFilters = DeviceFilter .getDeviceFilters(mActivity.getApplicationContext(), R.xml.device_filter); - if (mUSBMonitor == null || deviceFilters == null) -// throw new NullPointerException("mUSBMonitor ="+mUSBMonitor+"deviceFilters=;"+deviceFilters); + if (mUSBMonitor == null || deviceFilters == null) { + // throw new NullPointerException("mUSBMonitor ="+mUSBMonitor+"deviceFilters=;"+deviceFilters); return null; + } // matching all of filter devices return mUSBMonitor.getDeviceList(deviceFilters); } - public void capturePicture(String savePath,AbstractUVCCameraHandler.OnCaptureListener listener) { + public void capturePicture(String savePath, AbstractUVCCameraHandler.OnCaptureListener listener) { if (mCameraHandler != null && mCameraHandler.isOpened()) { File file = new File(savePath); - if(! Objects.requireNonNull(file.getParentFile()).exists()) { + if (!Objects.requireNonNull(file.getParentFile()).exists()) { file.getParentFile().mkdirs(); } - mCameraHandler.captureStill(savePath,listener); + mCameraHandler.captureStill(savePath, listener); } } @@ -266,7 +269,7 @@ public class UVCCameraHelper { public void startPusher(RecordParams params, AbstractUVCCameraHandler.OnEncodeResultListener listener) { if (mCameraHandler != null && !isPushing()) { - if(params.isSupportOverlay()) { + if (params.isSupportOverlay()) { TxtOverlay.install(mActivity.getApplicationContext()); } mCameraHandler.startRecording(params, listener); @@ -309,7 +312,7 @@ public class UVCCameraHelper { } public void setOnPreviewFrameListener(AbstractUVCCameraHandler.OnPreViewResultListener listener) { - if(mCameraHandler != null) { + if (mCameraHandler != null) { mCameraHandler.setOnPreViewResultListener(listener); } } @@ -346,8 +349,8 @@ public class UVCCameraHelper { return mCameraHandler.getSupportedPreviewSizes(); } - public void setDefaultPreviewSize(int defaultWidth,int defaultHeight) { - if(mUSBMonitor != null) { + public void setDefaultPreviewSize(int defaultWidth, int defaultHeight) { + if (mUSBMonitor != null) { throw new IllegalStateException("setDefaultPreviewSize should be call before initMonitor"); } this.previewWidth = defaultWidth; @@ -355,7 +358,7 @@ public class UVCCameraHelper { } public void setDefaultFrameFormat(int format) { - if(mUSBMonitor != null) { + if (mUSBMonitor != null) { throw new IllegalStateException("setDefaultFrameFormat should be call before initMonitor"); } this.mFrameFormat = format; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/AbstractRendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/AbstractRendererHolder.java new file mode 100644 index 0000000000..570dc75eb4 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/AbstractRendererHolder.java @@ -0,0 +1,1524 @@ +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 mClients + = new SparseArray(); + 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); + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/DumbRenderer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/DumbRenderer.java new file mode 100644 index 0000000000..2f4f5db2f0 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/DumbRenderer.java @@ -0,0 +1,248 @@ +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 android.graphics.SurfaceTexture; +import android.util.Log; +import android.view.Surface; + +import androidx.annotation.NonNull; + +/** + * OpenGL|ESでのSurfaceへの描画処理をDelegaterを介して行うためのIRenderer + */ +public class DumbRenderer implements IRenderer { +// private static final boolean DEBUG = BuildConfig.DEBUG && false; + private static final String TAG = DumbRenderer.class.getSimpleName(); + + public interface RendererDelegater { + public void onStart(final EGLBase eglBase); + public void onStop(final EGLBase eglBase); + public void onSetSurface(final EGLBase eglBase, final Object surface); + public void onResize(final EGLBase eglBase, final int width, final int height); + /** + * 描画実行 + * @param eglBase + * @param args #requestRenderの引数 + */ + public void onDraw(final EGLBase eglBase, final Object... args); + public void onMirror(final EGLBase eglBase, final int mirror); + } + + /** レンダリングスレッドの排他制御用オブジェクト */ + private final Object mSync = new Object(); + private RendererTask mRendererTask; + @MirrorMode + private int mMirror = MIRROR_NORMAL; + + public DumbRenderer(final EGLBase.IContext sharedContext, + final int flags, final RendererDelegater delegater) { + + this(3, sharedContext, flags, delegater); + } + + public DumbRenderer(final int maxClientVersion, + final EGLBase.IContext sharedContext, + final int flags, final RendererDelegater delegater) { + + mRendererTask = new RendererTask(maxClientVersion, sharedContext, flags, delegater); + new Thread(mRendererTask, TAG).start(); + if (!mRendererTask.waitReady()) { + // 初期化に失敗した時 + throw new RuntimeException("failed to start renderer thread"); + } + } + + @Override + public void release() { + synchronized (mSync) { + if (mRendererTask != null) { + // 描画タスクを開放 + mRendererTask.release(); + mRendererTask = null; + } + } + } + + @Override + public void setSurface(final Surface surface) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_SET_SURFACE, surface); + } + } + } + + @Override + public void setSurface(final SurfaceTexture surface) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_SET_SURFACE, surface); + } + } + } + + @Override + public void setMirror(@MirrorMode final int mirror) { + synchronized (mSync) { + if (mMirror != mirror) { + mMirror = mirror; + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_MIRROR, mirror % 4); + } + } + } + } + + @Override + @MirrorMode + public int getMirror() { + return mMirror; + } + + @Override + public void resize(final int width, final int height) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_RESIZE, width, height); + } + } + } + + @Override + public void requestRender(final Object... args) { + synchronized (mSync) { + if (mRendererTask != null) { + mRendererTask.offer(REQUEST_DRAW, args); + } + } + } + + private static final int REQUEST_SET_SURFACE = 1; + private static final int REQUEST_DRAW = 2; + private static final int REQUEST_RESIZE = 3; + private static final int REQUEST_MIRROR = 4; + + private static class RendererTask extends EglTask { + private final RendererDelegater mDelegater; + /** 最後にレンダリングしたフレームサイズ, 0ならまで一度も描画されていない */ + private int frameWidth, frameHeight, frameRotation; + /** 描画先Surfaceのサイズ */ + private int surfaceWidth, surfaceHeight; + /** 映像を左右反転させるかどうか */ + private boolean mirror; + + public RendererTask(final EGLBase.IContext sharedContext, + final int flags, @NonNull final RendererDelegater delegater) { + + this(3, sharedContext, flags, delegater); + } + + public RendererTask(final int maxClientVersion, + final EGLBase.IContext sharedContext, + final int flags, @NonNull final RendererDelegater delegater) { + + super(maxClientVersion, sharedContext, flags); + mDelegater = delegater; + } + + @Override + protected void onStart() { + makeCurrent(); + try { + mDelegater.onStart(getEgl()); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + @Override + protected void onStop() { + makeCurrent(); + try { + mDelegater.onStop(getEgl()); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) throws TaskBreak { + + switch (request) { + case REQUEST_SET_SURFACE: + handleSetSurface(obj); + break; + case REQUEST_DRAW: + handleDraw(obj); + break; + case REQUEST_RESIZE: + handleResize(arg1, arg2); + break; + case REQUEST_MIRROR: + handleMirror(arg1); + break; + } + return null; + } + + private void handleSetSurface(final Object surface) { + makeCurrent(); + try { + mDelegater.onSetSurface(getEgl(), surface); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + private void handleResize(final int width, final int height) { + if ((surfaceWidth != width) || (surfaceHeight != height)) { + surfaceWidth = width; + surfaceHeight = height; + makeCurrent(); + try { + mDelegater.onResize(getEgl(), width, height); + } catch (final Exception e) { + Log.w(TAG, e); + } + handleDraw(); + } + } + + private void handleDraw(final Object... args) { + makeCurrent(); + try { + mDelegater.onDraw(getEgl(), args); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + + private void handleMirror(final int mirror) { + makeCurrent(); + try { + mDelegater.onMirror(getEgl(), mirror); + } catch (final Exception e) { + Log.w(TAG, e); + } + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase.java new file mode 100644 index 0000000000..1a5b9fe61e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase.java @@ -0,0 +1,183 @@ +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 android.os.Build; + +/** + * EGLレンダリングコンテキストを生成&使用するためのヘルパークラス + */ +public abstract class EGLBase { + public static final Object EGL_LOCK = new Object(); + + public static final int EGL_RECORDABLE_ANDROID = 0x3142; + public static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public static final int EGL_OPENGL_ES2_BIT = 4; + public static final int EGL_OPENGL_ES3_BIT_KHR = 0x0040; +// public static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400; + + /** + * EGL生成のヘルパーメソッド, 環境に応じてEGLBase10またはEGLBase14を生成する + * maxClientVersion=3, ステンシルバッファなし + * @param sharedContext + * @param withDepthBuffer + * @param isRecordable + * @return + */ + public static EGLBase createFrom(final IContext sharedContext, + final boolean withDepthBuffer, final boolean isRecordable) { + + return createFrom(3, sharedContext, withDepthBuffer, 0, isRecordable); + } + + /** + * EGL生成のヘルパーメソッド, 環境に応じてEGLBase10またはEGLBase14を生成する + * maxClientVersion=3 + * @param sharedContext + * @param withDepthBuffer + * @param stencilBits + * @param isRecordable + * @return + */ + public static EGLBase createFrom(final IContext sharedContext, + final boolean withDepthBuffer, final int stencilBits, final boolean isRecordable) { + + return createFrom(3, sharedContext, + withDepthBuffer, stencilBits, isRecordable); + } + + /** + * EGL生成のヘルパーメソッド, 環境に応じてEGLBase10またはEGLBase14を生成する + * @param maxClientVersion + * @param sharedContext + * @param withDepthBuffer trueなら16ビットのデプスバッファ有り, falseならデプスバッファなし + * @param stencilBits 0以下ならステンシルバッファなし + * @param isRecordable + * @return + */ + public static EGLBase createFrom(final int maxClientVersion, + final IContext sharedContext, final boolean withDepthBuffer, + final int stencilBits, final boolean isRecordable) { + + if (isEGL14Supported() && ((sharedContext == null) + || (sharedContext instanceof EGLBase14.Context))) { + + return new EGLBase14(maxClientVersion, + (EGLBase14.Context)sharedContext, + withDepthBuffer, stencilBits, isRecordable); + } else { + return new EGLBase10(maxClientVersion, + (EGLBase10.Context)sharedContext, + withDepthBuffer, stencilBits, isRecordable); + } + } + + /** + * EGLレンダリングコンテキストのホルダークラス + */ + public static abstract class IContext { + public abstract long getNativeHandle(); + public abstract Object getEGLContext(); + } + + /** + * EGLコンフィグのホルダークラス + */ + public static abstract class IConfig { + } + + /** + * EGLレンダリングコンテキストに紐付ける描画オブジェクト + */ + public interface IEglSurface { + public void makeCurrent(); + public void swap(); + + public IContext getContext(); + /** + * swap with presentation time[ns] + * only works well now when using EGLBase14 + * @param presentationTimeNs + */ + public void swap(final long presentationTimeNs); + public void release(); + public boolean isValid(); + } + + public static boolean isEGL14Supported() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + /** + * 関連するリソースを破棄する + */ + public abstract void release(); + /** + * GLESに文字列を問い合わせる + * @param what + * @return + */ + public abstract String queryString(final int what); + /** + * GLESバージョンを取得する + * @return 1, 2または3 + */ + public abstract int getGlVersion(); + /** + * EGLレンダリングコンテキストを取得する + * このEGLBaseインスタンスを使って生成したEglSurfaceをmakeCurrentした状態で + * eglGetCurrentContextを呼び出すのと一緒 + * @return + */ + public abstract IContext getContext(); + + /** + * EGLコンフィグを取得する + * @return + */ + public abstract IConfig getConfig(); + + /** + * 指定したSurfaceからEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param nativeWindow Surface/SurfaceTexture/SurfaceHolder + * @return + */ + public abstract IEglSurface createFromSurface(final Object nativeWindow); + /** + * 指定した大きさのオフスクリーンEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param width PBufferオフスクリーンのサイズ(0以下はだめ) + * @param height + * @return + */ + public abstract IEglSurface createOffscreen(final int width, final int height); + /** + * EGLレンダリングコンテキストとスレッドの紐付けを解除する + */ + public abstract void makeDefault(); + + /** + * eglWaitGLとeglWaitNativeを呼ぶ + * + * eglWaitGL: コマンドキュー内のコマンドをすべて転送する, GLES20.glFinish()と同様の効果 + * eglWaitNative: GPU側の描画処理が終了するまで実行をブロックする + */ + public abstract void sync(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase10.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase10.java new file mode 100644 index 0000000000..c5ae0e2dd8 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase10.java @@ -0,0 +1,724 @@ +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 android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.opengl.GLES10; +import android.opengl.GLES20; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.serenegiant.utils.BuildCheck; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +/** + * EGLレンダリングコンテキストを生成&使用するためのヘルパークラス + */ +/*package*/ class EGLBase10 extends EGLBase { +// private static final boolean DEBUG = false; // FIXME set false on release + private static final String TAG = "EGLBase10"; + + private EGL10 mEgl = null; + private EGLDisplay mEglDisplay = null; + private Config mEglConfig = null; + private int mGlVersion = 2; + + private static final Context EGL_NO_CONTEXT = new Context(EGL10.EGL_NO_CONTEXT); + @NonNull private Context mContext = EGL_NO_CONTEXT; + + /** + * EGLレンダリングコンテキストのホルダークラス + */ + public static class Context extends IContext { + public final EGLContext eglContext; + + private Context(final EGLContext context) { + eglContext = context; + } + + @Override + public long getNativeHandle() { + return 0L; + } + + @Override + public Object getEGLContext() { + return eglContext; + } + } + + public static class Config extends IConfig { + public final EGLConfig eglConfig; + + private Config(final EGLConfig eglConfig) { + this.eglConfig = eglConfig; + } + } + + /** + * Android4.1.2だとSurfaceを使えない。 + * SurfaceTexture/SurfaceHolderの場合は内部でSurfaceを生成して使っているにもかかわらず。 + * SurfaceHolderはインターフェースなのでSurfaceHolderを継承したダミークラスを生成して食わす + */ + public static class MySurfaceHolder implements SurfaceHolder { + private final Surface surface; + + public MySurfaceHolder(final Surface surface) { + this.surface = surface; + } + @Override + public Surface getSurface() { + return surface; + } + // ここより下はどないでもええ + @Override + public void addCallback(final Callback callback) { + } + @Override + public void removeCallback(final Callback callback) { + } + @Override + public boolean isCreating() { + return false; + } + @Override + public void setType(final int type) { + } + @Override + public void setFixedSize(final int width, final int height) { + } + @Override + public void setSizeFromLayout() { + } + @Override + public void setFormat(final int format) { + } + @Override + public void setKeepScreenOn(final boolean screenOn) { + } + @Override + public Canvas lockCanvas() { + return null; + } + @Override + public Canvas lockCanvas(final Rect dirty) { + return null; + } + @Override + public void unlockCanvasAndPost(final Canvas canvas) { + } + @Override + public Rect getSurfaceFrame() { + return null; + } + } + + /** + * EGLレンダリングコンテキストに紐付ける描画オブジェクト + */ + public static class EglSurface implements IEglSurface { + private final EGLBase10 mEglBase; + private EGLSurface mEglSurface = EGL10.EGL_NO_SURFACE; + + /** + * Surface(Surface/SurfaceTexture/SurfaceHolder)に関係付けられたEglSurface + * @param eglBase + * @param surface + */ + private EglSurface(final EGLBase10 eglBase, final Object surface) + throws IllegalArgumentException { + +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((surface instanceof Surface) && !BuildCheck.isAndroid4_2()) { + // Android4.1.2だとSurfaceを使えない。 + // SurfaceTexture/SurfaceHolderの場合は内部で + // Surfaceを生成して使っているにもかかわらず。 + // SurfaceHolderはインターフェースなのでSurfaceHolderを + // 継承したダミークラスを生成して食わす + mEglSurface = mEglBase.createWindowSurface( + new MySurfaceHolder((Surface) surface)); + } else if ((surface instanceof Surface) + || (surface instanceof SurfaceHolder) + || (surface instanceof SurfaceTexture) + || (surface instanceof SurfaceView)) { + mEglSurface = mEglBase.createWindowSurface(surface); + } else { + throw new IllegalArgumentException("unsupported surface"); + } + } + + /** + * 指定した大きさを持つオフスクリーンEglSurface(PBuffer) + * @param eglBase + * @param width + * @param height + */ + private EglSurface(final EGLBase10 eglBase, final int width, final int height) { +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((width <= 0) || (height <= 0)) { + mEglSurface = mEglBase.createOffscreenSurface(1, 1); + } else { + mEglSurface = mEglBase.createOffscreenSurface(width, height); + } + } + + /** + * 指定したEGLSurfaceをカレントの描画Surfaceに設定する + * Surface全面に描画できるようにViewportも変更するので必要であればswapの後に変更すること + */ + @Override + public void makeCurrent() { + mEglBase.makeCurrent(mEglSurface); + if (mEglBase.getGlVersion() >= 2) { + GLES20.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), mEglBase.getSurfaceHeight(mEglSurface)); + } else { + GLES10.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), mEglBase.getSurfaceHeight(mEglSurface)); + } + } + + /** + * 描画を終了してダブルバッファを切り替える + */ + @Override + public void swap() { + mEglBase.swap(mEglSurface); + } + + @Override + public void swap(final long presentationTimeNs) { + mEglBase.swap(mEglSurface, presentationTimeNs); + } + + @Override + public IContext getContext() { + return mEglBase.getContext(); + } + + public void setPresentationTime(final long presentationTimeNs) { +// EGLExt.eglPresentationTimeANDROID(mEglBase.mEglDisplay, +// mEglSurface, presentationTimeNs); + } + + /** + * EGLSurfaceが有効かどうかを取得 + * @return + */ + @Override + public boolean isValid() { + return (mEglSurface != null) + && (mEglSurface != EGL10.EGL_NO_SURFACE) + && (mEglBase.getSurfaceWidth(mEglSurface) > 0) + && (mEglBase.getSurfaceHeight(mEglSurface) > 0); + } + + /** + * 破棄処理 + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "EglSurface:release:"); + mEglBase.makeDefault(); + mEglBase.destroyWindowSurface(mEglSurface); + mEglSurface = EGL10.EGL_NO_SURFACE; + } + } + + /** + * コンストラクタ + * @param maxClientVersion + * @param sharedContext 共有コンテキストを使用する場合に指定 + * @param withDepthBuffer + * @param isRecordable true MediaCodec等の録画用Surfaceを使用する場合に、 + * EGL_RECORDABLE_ANDROIDフラグ付きでコンフィグする + */ + public EGLBase10(final int maxClientVersion, + final Context sharedContext, final boolean withDepthBuffer, + final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "Constructor:"); + init(maxClientVersion, sharedContext, withDepthBuffer, stencilBits, isRecordable); + } + + /** + * 関連するリソースを破棄する + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + destroyContext(); + mContext = EGL_NO_CONTEXT; + if (mEgl == null) return; + mEgl.eglMakeCurrent(mEglDisplay, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); +// mEgl.eglReleaseThread(); // XXX これを入れるとハングアップする機種がある + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + mEglConfig = null; + mEgl = null; + } + + /** + * 指定したSurfaceからEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param nativeWindow Surface/SurfaceTexture/SurfaceHolder + * @return + */ + @Override + public EglSurface createFromSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createFromSurface:"); + final EglSurface eglSurface = new EglSurface(this, nativeWindow); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * 指定した大きさのオフスクリーンEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param width PBufferオフスクリーンのサイズ(0以下はだめ) + * @param height + * @return + */ + @Override + public EglSurface createOffscreen(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreen:"); + final EglSurface eglSurface = new EglSurface(this, width, height); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * EGLレンダリングコンテキストを取得する + * このEGLBaseインスタンスを使って生成したEglSurfaceをmakeCurrentした状態で + * eglGetCurrentContextを呼び出すのと一緒 + * @return + */ + @Override + public Context getContext() { + return mContext; + } + + /** + * EGLコンフィグを取得する + * @return + */ + @Override + public Config getConfig() { + return mEglConfig; + } + + /** + * EGLレンダリングコンテキストとスレッドの紐付けを解除する + */ + @Override + public void makeDefault() { +// if (DEBUG) Log.v(TAG, "makeDefault:"); + if (!mEgl.eglMakeCurrent(mEglDisplay, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)) { + + Log.w(TAG, "makeDefault:eglMakeCurrent:err=" + mEgl.eglGetError()); + } + } + + /** + * eglWaitGLとeglWaitNativeを呼ぶ + * + * eglWaitGL: コマンドキュー内のコマンドをすべて転送する, GLES20.glFinish()と同様の効果 + * eglWaitNative: GPU側の描画処理が終了するまで実行をブロックする + */ + @Override + public void sync() { + mEgl.eglWaitGL(); // GLES20.glFinish()と同様の効果 + mEgl.eglWaitNative(EGL10.EGL_CORE_NATIVE_ENGINE, null); + } + + /** + * GLESに文字列を問い合わせる + * @param what + * @return + */ + @Override + public String queryString(final int what) { + return mEgl.eglQueryString(mEglDisplay, what); + } + + /** + * GLESバージョンを取得する + * @return 1, 2または3 + */ + @Override + public int getGlVersion() { + return mGlVersion; + } + + /** + * 初期化の下請け + * @param maxClientVersion + * @param sharedContext + * @param withDepthBuffer + * @param isRecordable + */ + private final void init(final int maxClientVersion, + @Nullable Context sharedContext, + final boolean withDepthBuffer, final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "init:"); + sharedContext = (sharedContext != null) ? sharedContext : EGL_NO_CONTEXT; + if (mEgl == null) { + mEgl = (EGL10)EGLContext.getEGL(); + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + // EGLのバージョンを取得 + final int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + mEglDisplay = null; + throw new RuntimeException("eglInitialize failed"); + } + } + EGLConfig config; + if (maxClientVersion >= 3) { + // GLES3で取得できるかどうか試してみる + config = getConfig(3, withDepthBuffer, stencilBits, isRecordable); + if (config != null) { + final EGLContext context = createContext(sharedContext, config, 3); + if ((mEgl.eglGetError()) == EGL10.EGL_SUCCESS) { + // ここは例外生成したくないのでcheckEglErrorの代わりに自前でチェック + //Log.d(TAG, "Got GLES 3 config"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 3; + } + } + } + // GLES3で取得できなかった時はGLES2を試みる + if ((maxClientVersion >= 2) + && ((mContext == null) || (mContext.eglContext == EGL10.EGL_NO_CONTEXT))) { + + config = getConfig(2, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + try { + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } catch (final Exception e) { + if (isRecordable) { + config = getConfig(2, withDepthBuffer, stencilBits, false); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } + } + } + if ((mContext == null) || (mContext.eglContext == EGL10.EGL_NO_CONTEXT)) { + config = getConfig(1, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 1); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 1; + } + // confirm whether the EGL rendering context is successfully created + final int[] values = new int[1]; + mEgl.eglQueryContext(mEglDisplay, + mContext.eglContext, EGL_CONTEXT_CLIENT_VERSION, values); + Log.d(TAG, "EGLContext created, client version " + values[0]); + makeDefault(); + } + + /** + * change context to draw this window surface + * @return + */ + private final boolean makeCurrent(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "makeCurrent:"); +/* if (mEglDisplay == null) { + if (DEBUG) Log.d(TAG, "makeCurrent:eglDisplay not initialized"); + } */ + if (surface == null || surface == EGL10.EGL_NO_SURFACE) { + final int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "makeCurrent:EGL_BAD_NATIVE_WINDOW"); + } + return false; + } + // attach EGL rendering context to specific EGL window surface + if (!mEgl.eglMakeCurrent(mEglDisplay, surface, surface, mContext.eglContext)) { + Log.w("TAG", "eglMakeCurrent" + mEgl.eglGetError()); + return false; + } + return true; + } + + private final int swap(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "swap:"); + if (!mEgl.eglSwapBuffers(mEglDisplay, surface)) { + final int err = mEgl.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL10.EGL_SUCCESS; + } + + /** + * swap rendering buffer with presentation time[ns] + * presentationTimeNs is ignored on this method + * @param surface + * @param ignored + * @return + */ + private final int swap(final EGLSurface surface, final long ignored) { +// if (DEBUG) Log.v(TAG, "swap:"); +// EGLExt.eglPresentationTimeANDROID(mEglDisplay, surface, presentationTimeNs); + if (!mEgl.eglSwapBuffers(mEglDisplay, surface)) { + final int err = mEgl.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL10.EGL_SUCCESS; + } + + private final EGLContext createContext( + @NonNull final Context sharedContext, + final EGLConfig config, final int version) { + +// if (DEBUG) Log.v(TAG, "createContext:"); + + final int[] attrib_list = { + EGL_CONTEXT_CLIENT_VERSION, version, + EGL10.EGL_NONE + }; + final EGLContext context = mEgl.eglCreateContext( + mEglDisplay, config, sharedContext.eglContext, attrib_list); +// checkEglError("eglCreateContext"); + return context; + } + + private final void destroyContext() { +// if (DEBUG) Log.v(TAG, "destroyContext:"); + + if (!mEgl.eglDestroyContext(mEglDisplay, mContext.eglContext)) { + Log.e("destroyContext", "display:" + mEglDisplay + + " context: " + mContext.eglContext); + Log.e(TAG, "eglDestroyContext:" + mEgl.eglGetError()); + } + mContext = EGL_NO_CONTEXT; + } + + private final int getSurfaceWidth(final EGLSurface surface) { + final int[] value = new int[1]; + final boolean ret = mEgl.eglQuerySurface(mEglDisplay, + surface, EGL10.EGL_WIDTH, value); + if (!ret) value[0] = 0; + return value[0]; + } + + private final int getSurfaceHeight(final EGLSurface surface) { + final int[] value = new int[1]; + final boolean ret = mEgl.eglQuerySurface(mEglDisplay, + surface, EGL10.EGL_HEIGHT, value); + if (!ret) value[0] = 0; + return value[0]; + } + + /** + * nativeWindow should be one of the SurfaceView, Surface, SurfaceHolder and SurfaceTexture + * @param nativeWindow + * @return + */ + private final EGLSurface createWindowSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createWindowSurface:nativeWindow=" + nativeWindow); + + final int[] surfaceAttribs = { + EGL10.EGL_NONE + }; + EGLSurface result = null; + try { + result = mEgl.eglCreateWindowSurface(mEglDisplay, + mEglConfig.eglConfig, nativeWindow, surfaceAttribs); + if (result == null || result == EGL10.EGL_NO_SURFACE) { + final int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + throw new RuntimeException("createWindowSurface failed error=" + error); + } + makeCurrent(result); + // 画面サイズ・フォーマットの取得 + } catch (final Exception e) { + Log.e(TAG, "eglCreateWindowSurface", e); + throw new IllegalArgumentException(e); + } + return result; + } + + /** + * Creates an EGL surface associated with an offscreen buffer. + */ + private final EGLSurface createOffscreenSurface(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreenSurface:"); + final int[] surfaceAttribs = { + EGL10.EGL_WIDTH, width, + EGL10.EGL_HEIGHT, height, + EGL10.EGL_NONE + }; + mEgl.eglWaitGL(); + EGLSurface result = null; + try { + result = mEgl.eglCreatePbufferSurface(mEglDisplay, + mEglConfig.eglConfig, surfaceAttribs); + checkEglError("eglCreatePbufferSurface"); + if (result == null) { + throw new RuntimeException("surface was null"); + } + } catch (final IllegalArgumentException e) { + Log.e(TAG, "createOffscreenSurface", e); + } catch (final RuntimeException e) { + Log.e(TAG, "createOffscreenSurface", e); + } + return result; + } + + private final void destroyWindowSurface(EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "destroySurface:"); + + if (surface != EGL10.EGL_NO_SURFACE) { + mEgl.eglMakeCurrent(mEglDisplay, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, surface); + } + surface = EGL10.EGL_NO_SURFACE; +// if (DEBUG) Log.v(TAG, "destroySurface:finished"); + } + + private final void checkEglError(final String msg) { + int error; + if ((error = mEgl.eglGetError()) != EGL10.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } + + private final EGLConfig getConfig(final int version, + final boolean hasDepthBuffer, final int stencilBits, final boolean isRecordable) { + + int renderableType = EGL_OPENGL_ES2_BIT; + if (version >= 3) { + renderableType |= EGL_OPENGL_ES3_BIT_KHR; + } +// final int swapBehavior = dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + final int[] attribList = { + EGL10.EGL_RENDERABLE_TYPE, renderableType, + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, +// EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT | swapBehavior, + EGL10.EGL_NONE, EGL10.EGL_NONE, //EGL10.EGL_STENCIL_SIZE, 8, + // this flag need to recording of MediaCodec + EGL10.EGL_NONE, EGL10.EGL_NONE, // EGL_RECORDABLE_ANDROID, 1, + EGL10.EGL_NONE, EGL10.EGL_NONE, // with_depth_buffer ? EGL10.EGL_DEPTH_SIZE : EGL10.EGL_NONE, + // with_depth_buffer ? 16 : 0, + EGL10.EGL_NONE + }; + int offset = 10; + if (stencilBits > 0) { // ステンシルバッファ(常時未使用) + attribList[offset++] = EGL10.EGL_STENCIL_SIZE; + attribList[offset++] = 8; + } + if (hasDepthBuffer) { // デプスバッファ + attribList[offset++] = EGL10.EGL_DEPTH_SIZE; + attribList[offset++] = 16; + } + if (isRecordable && BuildCheck.isAndroid4_3()) { + // MediaCodecの入力用Surfaceの場合 + // A-1000F(Android4.1.2)はこのフラグをつけるとうまく動かない + attribList[offset++] = EGL_RECORDABLE_ANDROID; + attribList[offset++] = 1; + } + for (int i = attribList.length - 1; i >= offset; i--) { + attribList[i] = EGL10.EGL_NONE; + } + EGLConfig config = internalGetConfig(attribList); + if ((config == null) && (version == 2)) { + if (isRecordable) { + // EGL_RECORDABLE_ANDROIDをつけると失敗する機種もあるので取り除く + final int n = attribList.length; + for (int i = 10; i < n - 1; i += 2) { + if (attribList[i] == EGL_RECORDABLE_ANDROID) { + for (int j = i; j < n; j++) { + attribList[j] = EGL10.EGL_NONE; + } + break; + } + } + config = internalGetConfig(attribList); + } + } + if (config == null) { + Log.w(TAG, "try to fallback to RGB565"); + attribList[3] = 5; + attribList[5] = 6; + attribList[7] = 5; + config = internalGetConfig(attribList); + } + return config; + } + + private EGLConfig internalGetConfig(final int[] attribList) { + final EGLConfig[] configs = new EGLConfig[1]; + final int[] numConfigs = new int[1]; + if (!mEgl.eglChooseConfig(mEglDisplay, + attribList, configs, configs.length, numConfigs)) { + + return null; + } + return configs[0]; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase14.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase14.java new file mode 100644 index 0000000000..fb119a4641 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EGLBase14.java @@ -0,0 +1,631 @@ +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 android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.opengl.GLES10; +import android.opengl.GLES20; +import android.os.Build; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.annotation.NonNull; + +import com.serenegiant.utils.BuildCheck; + +/** + * EGLレンダリングコンテキストを生成&使用するためのヘルパークラス + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +/*package*/ class EGLBase14 extends EGLBase { // API >= 17 +// private static final boolean DEBUG = false; // TODO set false on release + private static final String TAG = "EGLBase14"; + + private static final Context EGL_NO_CONTEXT = new Context(EGL14.EGL_NO_CONTEXT); + + private Config mEglConfig = null; + @NonNull private Context mContext = EGL_NO_CONTEXT; +// private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT; + private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY; + private EGLContext mDefaultContext = EGL14.EGL_NO_CONTEXT; + private int mGlVersion = 2; + + /** + * EGLレンダリングコンテキストのホルダークラス + */ + public static class Context extends IContext { + public final EGLContext eglContext; + + private Context(final EGLContext context) { + eglContext = context; + } + + @Override + @SuppressLint("NewApi") + public long getNativeHandle() { + return eglContext != null ? + (BuildCheck.isLollipop() + ? eglContext.getNativeHandle() : eglContext.getHandle()) : 0L; + } + + @Override + public Object getEGLContext() { + return eglContext; + } + } + + public static class Config extends IConfig { + public final EGLConfig eglConfig; + + private Config(final EGLConfig eglConfig) { + this.eglConfig = eglConfig; + } + } + + /** + * EGLレンダリングコンテキストに紐付ける描画オブジェクト + */ + public static class EglSurface implements IEglSurface { + private final EGLBase14 mEglBase; + private EGLSurface mEglSurface = EGL14.EGL_NO_SURFACE; + + private EglSurface(final EGLBase14 eglBase, final Object surface) + throws IllegalArgumentException { + +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((surface instanceof Surface) + || (surface instanceof SurfaceHolder) + || (surface instanceof SurfaceTexture) + || (surface instanceof SurfaceView)) { + mEglSurface = mEglBase.createWindowSurface(surface); + } else { + throw new IllegalArgumentException("unsupported surface"); + } + } + + /** + * 指定した大きさを持つオフスクリーンEglSurface(PBuffer) + * @param eglBase + * @param width + * @param height + */ + private EglSurface(final EGLBase14 eglBase, + final int width, final int height) { + +// if (DEBUG) Log.v(TAG, "EglSurface:"); + mEglBase = eglBase; + if ((width <= 0) || (height <= 0)) { + mEglSurface = mEglBase.createOffscreenSurface(1, 1); + } else { + mEglSurface = mEglBase.createOffscreenSurface(width, height); + } + } + + @Override + public void makeCurrent() { + mEglBase.makeCurrent(mEglSurface); + if (mEglBase.getGlVersion() >= 2) { + GLES20.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), + mEglBase.getSurfaceHeight(mEglSurface)); + } else { + GLES10.glViewport(0, 0, + mEglBase.getSurfaceWidth(mEglSurface), + mEglBase.getSurfaceHeight(mEglSurface)); + } + } + + @Override + public void swap() { + mEglBase.swap(mEglSurface); + } + + @Override + public void swap(final long presentationTimeNs) { + mEglBase.swap(mEglSurface, presentationTimeNs); + } + + public void setPresentationTime(final long presentationTimeNs) { + EGLExt.eglPresentationTimeANDROID(mEglBase.mEglDisplay, + mEglSurface, presentationTimeNs); + } + + @Override + public IContext getContext() { + return mEglBase.getContext(); + } + + @Override + public boolean isValid() { + return (mEglSurface != null) + && (mEglSurface != EGL14.EGL_NO_SURFACE) + && (mEglBase.getSurfaceWidth(mEglSurface) > 0) + && (mEglBase.getSurfaceHeight(mEglSurface) > 0); + } + + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "EglSurface:release:"); + mEglBase.makeDefault(); + mEglBase.destroyWindowSurface(mEglSurface); + mEglSurface = EGL14.EGL_NO_SURFACE; + } + } + + /** + * コンストラクタ + * @param maxClientVersion + * @param sharedContext + * @param withDepthBuffer + * @param isRecordable + */ + public EGLBase14(final int maxClientVersion, + final Context sharedContext, final boolean withDepthBuffer, + final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "Constructor:"); + init(maxClientVersion, sharedContext, withDepthBuffer, stencilBits, isRecordable); + } + + /** + * 関連するリソースを破棄する + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + if (mEglDisplay != EGL14.EGL_NO_DISPLAY) { + destroyContext(); + EGL14.eglTerminate(mEglDisplay); + EGL14.eglReleaseThread(); + } + mEglDisplay = EGL14.EGL_NO_DISPLAY; + mContext = EGL_NO_CONTEXT; + } + + /** + * 指定したSurfaceからEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param nativeWindow Surface/SurfaceTexture/SurfaceHolder + * @return + */ + @Override + public EglSurface createFromSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createFromSurface:"); + final EglSurface eglSurface = new EglSurface(this, nativeWindow); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * 指定した大きさのオフスクリーンEglSurfaceを生成する + * 生成したEglSurfaceをmakeCurrentした状態で戻る + * @param width PBufferオフスクリーンのサイズ(0以下はだめ) + * @param height + * @return + */ + @Override + public EglSurface createOffscreen(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreen:"); + final EglSurface eglSurface = new EglSurface(this, width, height); + eglSurface.makeCurrent(); + return eglSurface; + } + + /** + * GLESに文字列を問い合わせる + * @param what + * @return + */ + public String queryString(final int what) { + return EGL14.eglQueryString(mEglDisplay, what); + } + + /** + * GLESバージョンを取得する + * @return 1, 2または3 + */ + @Override + public int getGlVersion() { + return mGlVersion; + } + + /** + * EGLレンダリングコンテキストを取得する + * このEGLBaseインスタンスを使って生成したEglSurfaceをmakeCurrentした状態で + * eglGetCurrentContextを呼び出すのと一緒 + * @return + */ + @Override + public Context getContext() { + return mContext; + } + + /** + * EGLコンフィグを取得する + * @return + */ + @Override + public Config getConfig() { + return mEglConfig; + } + + /** + * EGLレンダリングコンテキストとスレッドの紐付けを解除する + */ + @Override + public void makeDefault() { +// if (DEBUG) Log.v(TAG, "makeDefault:"); + if (!EGL14.eglMakeCurrent(mEglDisplay, + EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) { + + Log.w("TAG", "makeDefault" + EGL14.eglGetError()); + } + } + + /** + * eglWaitGLとeglWaitNativeを呼ぶ + * + * eglWaitGL: コマンドキュー内のコマンドをすべて転送する, GLES20.glFinish()と同様の効果 + * eglWaitNative: GPU側の描画処理が終了するまで実行をブロックする + */ + @Override + public void sync() { + EGL14.eglWaitGL(); // GLES20.glFinish()と同様の効果 + EGL14.eglWaitNative(EGL14.EGL_CORE_NATIVE_ENGINE); + } + + private void init(final int maxClientVersion, Context sharedContext, + final boolean withDepthBuffer, final int stencilBits, final boolean isRecordable) { + +// if (DEBUG) Log.v(TAG, "init:"); + if (mEglDisplay != EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("EGL already set up"); + } + + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + // EGLのバージョンを取得 + final int[] version = new int[2]; + if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { + mEglDisplay = null; + throw new RuntimeException("eglInitialize failed"); + } + + sharedContext = (sharedContext != null) ? sharedContext : EGL_NO_CONTEXT; + + EGLConfig config; + if (maxClientVersion >= 3) { + // GLES3で取得できるかどうか試してみる + config = getConfig(3, withDepthBuffer, stencilBits, isRecordable); + if (config != null) { + final EGLContext context = createContext(sharedContext, config, 3); + if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { + // ここは例外生成したくないのでcheckEglErrorの代わりに自前でチェック + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 3; + } + } + } + // GLES3で取得できなかった時はGLES2を試みる + if ((maxClientVersion >= 2) + && ((mContext == null) || (mContext.eglContext == EGL14.EGL_NO_CONTEXT))) { + + config = getConfig(2, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + try { + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } catch (final Exception e) { + if (isRecordable) { + config = getConfig(2, withDepthBuffer, stencilBits, false); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 2); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 2; + } + } + } + if ((mContext == null) || (mContext.eglContext == EGL14.EGL_NO_CONTEXT)) { + config = getConfig(1, withDepthBuffer, stencilBits, isRecordable); + if (config == null) { + throw new RuntimeException("chooseConfig failed"); + } + // create EGL rendering context + final EGLContext context = createContext(sharedContext, config, 1); + checkEglError("eglCreateContext"); + mEglConfig = new Config(config); + mContext = new Context(context); + mGlVersion = 1; + } + // confirm whether the EGL rendering context is successfully created + final int[] values = new int[1]; + EGL14.eglQueryContext(mEglDisplay, + mContext.eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values, 0); + Log.d(TAG, "EGLContext created, client version " + values[0]); + makeDefault(); // makeCurrent(EGL14.EGL_NO_SURFACE); + } + + /** + * change context to draw this window surface + * @return + */ + private boolean makeCurrent(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "makeCurrent:"); +/* if (mEglDisplay == null) { + if (DEBUG) Log.d(TAG, "makeCurrent:eglDisplay not initialized"); + } */ + if (surface == null || surface == EGL14.EGL_NO_SURFACE) { + final int error = EGL14.eglGetError(); + if (error == EGL14.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "makeCurrent:returned EGL_BAD_NATIVE_WINDOW."); + } + return false; + } + // attach EGL rendering context to specific EGL window surface + if (!EGL14.eglMakeCurrent(mEglDisplay, surface, surface, mContext.eglContext)) { + Log.w("TAG", "eglMakeCurrent" + EGL14.eglGetError()); + return false; + } + return true; + } + + private int swap(final EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "swap:"); + if (!EGL14.eglSwapBuffers(mEglDisplay, surface)) { + final int err = EGL14.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL14.EGL_SUCCESS; + } + + private int swap(final EGLSurface surface, final long presentationTimeNs) { +// if (DEBUG) Log.v(TAG, "swap:"); + EGLExt.eglPresentationTimeANDROID(mEglDisplay, surface, presentationTimeNs); + if (!EGL14.eglSwapBuffers(mEglDisplay, surface)) { + final int err = EGL14.eglGetError(); +// if (DEBUG) Log.w(TAG, "swap:err=" + err); + return err; + } + return EGL14.EGL_SUCCESS; + } + + private EGLContext createContext(final Context sharedContext, + final EGLConfig config, final int version) { + +// if (DEBUG) Log.v(TAG, "createContext:"); + + final int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, version, + EGL14.EGL_NONE + }; + final EGLContext context = EGL14.eglCreateContext(mEglDisplay, + config, sharedContext.eglContext, attrib_list, 0); +// checkEglError("eglCreateContext"); + return context; + } + + private void destroyContext() { +// if (DEBUG) Log.v(TAG, "destroyContext:"); + + if (!EGL14.eglDestroyContext(mEglDisplay, mContext.eglContext)) { + Log.e("destroyContext", "display:" + mEglDisplay + + " context: " + mContext.eglContext); + Log.e(TAG, "eglDestroyContext:" + EGL14.eglGetError()); + } + mContext = EGL_NO_CONTEXT; + if (mDefaultContext != EGL14.EGL_NO_CONTEXT) { + if (!EGL14.eglDestroyContext(mEglDisplay, mDefaultContext)) { + Log.e("destroyContext", "display:" + mEglDisplay + + " context: " + mDefaultContext); + Log.e(TAG, "eglDestroyContext:" + EGL14.eglGetError()); + } + mDefaultContext = EGL14.EGL_NO_CONTEXT; + } + } + + private final int[] mSurfaceDimension = new int[2]; + private final int getSurfaceWidth(final EGLSurface surface) { + final boolean ret = EGL14.eglQuerySurface(mEglDisplay, + surface, EGL14.EGL_WIDTH, mSurfaceDimension, 0); + if (!ret) mSurfaceDimension[0] = 0; + return mSurfaceDimension[0]; + } + + private final int getSurfaceHeight(final EGLSurface surface) { + final boolean ret = EGL14.eglQuerySurface(mEglDisplay, + surface, EGL14.EGL_HEIGHT, mSurfaceDimension, 1); + if (!ret) mSurfaceDimension[1] = 0; + return mSurfaceDimension[1]; + } + + /** + * nativeWindow should be one of the Surface, SurfaceHolder and SurfaceTexture + * @param nativeWindow + * @return + */ + private final EGLSurface createWindowSurface(final Object nativeWindow) { +// if (DEBUG) Log.v(TAG, "createWindowSurface:nativeWindow=" + nativeWindow); + + final int[] surfaceAttribs = { + EGL14.EGL_NONE + }; + EGLSurface result = null; + try { + result = EGL14.eglCreateWindowSurface(mEglDisplay, + mEglConfig.eglConfig, nativeWindow, surfaceAttribs, 0); + if (result == null || result == EGL14.EGL_NO_SURFACE) { + final int error = EGL14.eglGetError(); + if (error == EGL14.EGL_BAD_NATIVE_WINDOW) { + Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + throw new RuntimeException("createWindowSurface failed error=" + error); + } + makeCurrent(result); + // 画面サイズ・フォーマットの取得 + } catch (final Exception e) { + Log.e(TAG, "eglCreateWindowSurface", e); + throw new IllegalArgumentException(e); + } + return result; + } + + /** + * Creates an EGL surface associated with an offscreen buffer. + */ + private final EGLSurface createOffscreenSurface(final int width, final int height) { +// if (DEBUG) Log.v(TAG, "createOffscreenSurface:"); + final int[] surfaceAttribs = { + EGL14.EGL_WIDTH, width, + EGL14.EGL_HEIGHT, height, + EGL14.EGL_NONE + }; + EGLSurface result = null; + try { + result = EGL14.eglCreatePbufferSurface(mEglDisplay, + mEglConfig.eglConfig, surfaceAttribs, 0); + checkEglError("eglCreatePbufferSurface"); + if (result == null) { + throw new RuntimeException("surface was null"); + } + } catch (final IllegalArgumentException e) { + Log.e(TAG, "createOffscreenSurface", e); + } catch (final RuntimeException e) { + Log.e(TAG, "createOffscreenSurface", e); + } + return result; + } + + private void destroyWindowSurface(EGLSurface surface) { +// if (DEBUG) Log.v(TAG, "destroySurface:"); + + if (surface != EGL14.EGL_NO_SURFACE) { + EGL14.eglMakeCurrent(mEglDisplay, + EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + EGL14.eglDestroySurface(mEglDisplay, surface); + } + surface = EGL14.EGL_NO_SURFACE; +// if (DEBUG) Log.v(TAG, "destroySurface:finished"); + } + + private void checkEglError(final String msg) { + int error; + if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } + + private EGLConfig getConfig(final int version, + final boolean hasDepthBuffer, final int stencilBits, final boolean isRecordable) { + + int renderableType = EGL_OPENGL_ES2_BIT; + if (version >= 3) { + renderableType |= EGL_OPENGL_ES3_BIT_KHR; + } + final int[] attribList = { + EGL14.EGL_RENDERABLE_TYPE, renderableType, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, +// EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT | swapBehavior, + EGL14.EGL_NONE, EGL14.EGL_NONE, //EGL14.EGL_STENCIL_SIZE, 8, + // this flag need to recording of MediaCodec + EGL14.EGL_NONE, EGL14.EGL_NONE, //EGL_RECORDABLE_ANDROID, 1, + EGL14.EGL_NONE, EGL14.EGL_NONE, // with_depth_buffer ? EGL14.EGL_DEPTH_SIZE : EGL14.EGL_NONE, + // with_depth_buffer ? 16 : 0, + EGL14.EGL_NONE + }; + int offset = 10; + if (stencilBits > 0) { // ステンシルバッファ(常時未使用) + attribList[offset++] = EGL14.EGL_STENCIL_SIZE; + attribList[offset++] = stencilBits; + } + if (hasDepthBuffer) { // デプスバッファ + attribList[offset++] = EGL14.EGL_DEPTH_SIZE; + attribList[offset++] = 16; + } + if (isRecordable && BuildCheck.isAndroid4_3()) {// MediaCodecの入力用Surfaceの場合 + attribList[offset++] = EGL_RECORDABLE_ANDROID; + attribList[offset++] = 1; + } + for (int i = attribList.length - 1; i >= offset; i--) { + attribList[i] = EGL14.EGL_NONE; + } + EGLConfig config = internalGetConfig(attribList); + if ((config == null) && (version == 2)) { + if (isRecordable) { + // EGL_RECORDABLE_ANDROIDをつけると失敗する機種もあるので取り除く + final int n = attribList.length; + for (int i = 10; i < n - 1; i += 2) { + if (attribList[i] == EGL_RECORDABLE_ANDROID) { + for (int j = i; j < n; j++) { + attribList[j] = EGL14.EGL_NONE; + } + break; + } + } + config = internalGetConfig(attribList); + } + } + if (config == null) { + Log.w(TAG, "try to fallback to RGB565"); + attribList[3] = 5; + attribList[5] = 6; + attribList[7] = 5; + config = internalGetConfig(attribList); + } + return config; + } + + private EGLConfig internalGetConfig(final int[] attribList) { + final EGLConfig[] configs = new EGLConfig[1]; + final int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(mEglDisplay, + attribList, 0, configs, 0, configs.length, numConfigs, 0)) { + return null; + } + return configs[0]; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EffectRendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EffectRendererHolder.java new file mode 100644 index 0000000000..5b72592870 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EffectRendererHolder.java @@ -0,0 +1,548 @@ +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.FRAGMENT_SHADER_SIMPLE_OES; +import static com.serenegiant.glutils.ShaderConst.FUNC_HSV2RGB; +import static com.serenegiant.glutils.ShaderConst.FUNC_RGB2HSV; +import static com.serenegiant.glutils.ShaderConst.HEADER_OES; +import static com.serenegiant.glutils.ShaderConst.SAMPLER_OES; +import static com.serenegiant.glutils.ShaderConst.SHADER_VERSION; + +import android.annotation.SuppressLint; +import android.opengl.GLES20; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * GL_TEXTURE_EXTERNAL_OESテクスチャを受け取ってSurfaceへ分配描画するクラス + * RendererHolderにフラグメントシェーダーでのフィルター処理を追加 + * ...カラーマトリックスを掛けるほうがいいかなぁ + * ...色はuniform変数で渡す方がいいかも + */ +public class EffectRendererHolder extends AbstractRendererHolder { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = EffectRendererHolder.class.getSimpleName(); + + private static final int MAX_PARAM_NUM = 18; + + public static final int EFFECT_NON = 0; + public static final int EFFECT_GRAY = 1; + public static final int EFFECT_GRAY_REVERSE = 2; + public static final int EFFECT_BIN = 3; + public static final int EFFECT_BIN_YELLOW = 4; + public static final int EFFECT_BIN_GREEN = 5; + public static final int EFFECT_BIN_REVERSE = 6; + public static final int EFFECT_BIN_REVERSE_YELLOW = 7; + public static final int EFFECT_BIN_REVERSE_GREEN = 8; + /** + * 赤色黄色を強調 + * setParamsはfloat[12] { + * 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + * 0.50f, 1.0f, // 強調する彩度下限, 上限 + * 0.40f, 1.0f, // 強調する明度下限, 上限 + * 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + * 1.0f, 0.5f, 0.8f, // 通常時のファクター(H, S, Vの順) 彩度(x0.5)と明度(x0.8)を少し落とす + * } + */ + public static final int EFFECT_EMPHASIZE_RED_YELLOW = 9; + /** + * 赤色黄色と白を強調 + * setParamsはfloat[12] { + * 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + * 0.50f, 1.0f, // 強調する彩度下限, 上限 + * 0.40f, 1.0f, // 強調する明度下限, 上限 + * 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + * 1.0f, 0.5f, 0.8f, // 通常時のファクター(H, S, Vの順) 彩度(x0.5)と明度(x0.8)を少し落とす + * 白のパラメータは今はなし + */ + public static final int EFFECT_EMPHASIZE_RED_YELLOW_WHITE = 10; + /** + * 黄色と白を強調 + * setParamsはfloat[12] { + * 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 FIXME 未調整 + * 0.50f, 1.0f, // 強調する彩度下限, 上限 + * 0.40f, 1.0f, // 強調する明度下限, 上限 + * 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + * 1.0f, 0.5f, 0.8f, // 通常時のファクター(H, S, Vの順) 彩度(x0.5)と明度(x0.8)を少し落とす + * 白のパラメータは今はなし + */ + public static final int EFFECT_EMPHASIZE_YELLOW_WHITE = 11; + /** 内蔵映像効果の数 */ + public static final int EFFECT_NUM = 12; + + /** + * グレースケール変換のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)を渡すこと + */ + private static final String FRAGMENT_SHADER_GRAY_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 cl3 = vec3(color, color, color);\n" + + " gl_FragColor = vec4(cl3, 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_GRAY_OES + = String.format(FRAGMENT_SHADER_GRAY_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 白黒反転したグレースケール変換のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)を渡すこと + */ + private static final String FRAGMENT_SHADER_GRAY_REVERSE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 cl3 = vec3(color, color, color);\n" + + " gl_FragColor = vec4(clamp(vec3(1.0, 1.0, 1.0) - cl3, 0.0, 1.0), 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_GRAY_REVERSE_OES + = String.format(FRAGMENT_SHADER_GRAY_REVERSE_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 2値化のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)、 + * 変換後の明るい部分用の色を指定するための文字列(R, G, Bの順)を渡すこと + */ + private static final String FRAGMENT_SHADER_BIN_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "const vec3 cl = vec3(%s);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 bin = step(0.3, vec3(color, color, color));\n" + + " gl_FragColor = vec4(cl * bin, 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_BIN_OES + = String.format(FRAGMENT_SHADER_BIN_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 1.0"); + + private static final String FRAGMENT_SHADER_BIN_YELLOW_OES + = String.format(FRAGMENT_SHADER_BIN_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 0.0"); + + private static final String FRAGMENT_SHADER_BIN_GREEN_OES + = String.format(FRAGMENT_SHADER_BIN_BASE, HEADER_OES, SAMPLER_OES, "0.0, 1.0, 0.0"); + + /** + * 反転した2値化のためのフラグメントシェーダーのベース文字列 + * header(HEADER_OESかHEADER_2D)とサンプラーの種類文字列(SAMPLER_OESかSAMPLER_2D)、 + * 変換後の明るい部分用の色を指定するための文字列(R, G, Bの順)を渡すこと + */ + private static final String FRAGMENT_SHADER_BIN_REVERSE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "const vec3 conv = vec3(0.3, 0.59, 0.11);\n" + + "const vec3 cl = vec3(%s);\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = dot(tc.rgb, conv);\n" + + " vec3 bin = step(0.3, vec3(color, color, color));\n" + + " gl_FragColor = vec4(cl * (vec3(1.0, 1.0, 1.0) - bin), 1.0);\n" + + "}\n"; + + private static final String FRAGMENT_SHADER_BIN_REVERSE_OES + = String.format(FRAGMENT_SHADER_BIN_REVERSE_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 1.0"); + + private static final String FRAGMENT_SHADER_BIN_REVERSE_YELLOW_OES + = String.format(FRAGMENT_SHADER_BIN_REVERSE_BASE, HEADER_OES, SAMPLER_OES, "1.0, 1.0, 0.0"); + + private static final String FRAGMENT_SHADER_BIN_REVERSE_GREEN_OES + = String.format(FRAGMENT_SHADER_BIN_REVERSE_BASE, HEADER_OES, SAMPLER_OES, "0.0, 1.0, 0.0"); + + /** + * 赤と黄色を強調するためのフラグメントシェーダーのベース文字列 + */ + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uParams[" + MAX_PARAM_NUM + "];\n" + + FUNC_RGB2HSV + + FUNC_HSV2RGB + + "void main() {\n" + + " vec3 hsv = rgb2hsv(texture2D(sTexture, vTextureCoord).rgb);\n" + // RGBをHSVに変換 + " if ( ((hsv.g >= uParams[2]) && (hsv.g <= uParams[3]))\n" + // s + " && ((hsv.b >= uParams[4]) && (hsv.b <= uParams[5]))\n" + // v + " && ((hsv.r <= uParams[0]) || (hsv.r >= uParams[1])) ) {\n" + // h + " hsv = hsv * vec3(uParams[6], uParams[7], uParams[8]);\n" + // 赤色と黄色の範囲 + " } else {\n" + + " hsv = hsv * vec3(uParams[9], uParams[10], uParams[11]);\n" + // それ以外なら + " }\n" + + " gl_FragColor = vec4(hsv2rgb(clamp(hsv, 0.0, 1.0)), 1.0);\n" + // HSVをRGBに戻す + "}\n"; + + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_OES + = String.format(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 赤と黄色と白色を強調するためのフラグメントシェーダーのベース文字列 + */ + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uParams[" + MAX_PARAM_NUM + "];\n" + + FUNC_RGB2HSV + + FUNC_HSV2RGB + + "void main() {\n" + + " vec3 hsv = rgb2hsv(texture2D(sTexture, vTextureCoord).rgb);\n" + // RGBをHSVに変換 + " if ( ((hsv.g >= uParams[2]) && (hsv.g <= uParams[3]))\n" + // s + " && ((hsv.b >= uParams[4]) && (hsv.b <= uParams[5]))\n" + // v + " && ((hsv.r <= uParams[0]) || (hsv.r >= uParams[1])) ) {\n" + // h + " hsv = hsv * vec3(uParams[6], uParams[7], uParams[8]);\n" + // 赤色と黄色の範囲 + " } else if ((hsv.g < uParams[12]) && (hsv.b < uParams[13])) {\n" + // 彩度が一定以下, 明度が一定以下なら + " hsv = hsv * vec3(1.0, 0.0, 2.0);\n" + // 色相そのまま, 彩度0, 明度x2 + " } else {\n" + + " hsv = hsv * vec3(uParams[9], uParams[10], uParams[11]);\n" + // それ以外なら + " }\n" + + " gl_FragColor = vec4(hsv2rgb(clamp(hsv, 0.0, 1.0)), 1.0);\n" + // HSVをRGBに戻す + "}\n"; + + private static final String FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_OES + = String.format(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_BASE, HEADER_OES, SAMPLER_OES); + + /** + * 黄色と白を強調するためのフラグメントシェーダーのベース文字列 + * 今はFRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_BASEと同じ(違うパラメータ渡せば良いだけなので) + */ + private static final String FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uParams[" + MAX_PARAM_NUM + "];\n" + + FUNC_RGB2HSV + + FUNC_HSV2RGB + + "void main() {\n" + + " vec3 rgb = texture2D(sTexture, vTextureCoord).rgb;\n" + // RGB + " vec3 hsv = rgb2hsv(rgb);\n" + // RGBをHSVに変換 + " if ( ((hsv.r >= uParams[0]) && (hsv.r <= uParams[1]))\n" + // h + " && ((hsv.g >= uParams[2]) && (hsv.g <= uParams[3]))\n" + // s + " && ((hsv.b >= uParams[4]) && (hsv.b <= uParams[5])) ) {\n" + // v + " hsv = hsv * vec3(uParams[6], uParams[7], uParams[8]);\n" + // 黄色の範囲 + " } else if ((hsv.g < uParams[12]) && (hsv.b > uParams[13])) {\n" + // 彩度が一定以下, 明度が一定以上なら + " hsv = hsv * vec3(1.0, 0.0, 2.0);\n" + // 色相そのまま, 彩度0, 明度x2 + " } else {\n" + + " hsv = hsv * vec3(uParams[9], uParams[10], uParams[11]);\n" + // それ以外なら + " }\n" + + " gl_FragColor = vec4(hsv2rgb(clamp(hsv, 0.0, 1.0)), 1.0);\n" + // HSVをRGBに戻す + "}\n"; + + private static final String FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_OES + = String.format(FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_BASE, HEADER_OES, SAMPLER_OES); + + public EffectRendererHolder(final int width, final int height, + @Nullable final RenderHolderCallback callback) { + + this(width, height, + 3, null, EglTask.EGL_FLAG_RECORDABLE, + callback); + } + + public EffectRendererHolder(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags, + @Nullable final RenderHolderCallback callback) { + + super(width, height, + maxClientVersion, sharedContext, flags, + callback); + } + + @Override + @NonNull + protected RendererTask createRendererTask(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags) { + return new MyRendererTask(this, width, height, + maxClientVersion, sharedContext, flags); + } + +//================================================================================ +// クラス固有publicメソッド +//================================================================================ + + /** + * 映像効果をセット + * 継承して独自の映像効果を追加する時はEFFECT_NUMよりも大きい値を使うこと + * @param effect + */ + public void changeEffect(final int effect) { + ((MyRendererTask)mRendererTask).changeEffect(effect); + } + + /** + * 現在の映像効果番号を取得 + * @return + */ + public int getCurrentEffect() { + return ((MyRendererTask)mRendererTask).mEffect; + } + + /** + * 現在選択中の映像フィルタにパラメータ配列をセット + * 現在対応しているのは色強調用の映像効果のみ(n=12以上必要) + * @param params + */ + public void setParams(@NonNull final float[] params) { + ((MyRendererTask)mRendererTask).setParams(-1, params); + } + + /** + * 指定した映像フィルタにパラメータ配列をセット + * 現在対応しているのは色強調用の映像効果のみ(n=12以上必要) + * @param effect EFFECT_NONより大きいこと + * @param params + * @throws IllegalArgumentException effectが範囲外ならIllegalArgumentException生成 + */ + public void setParams(final int effect, @NonNull final float[] params) + throws IllegalArgumentException { + + if (effect > EFFECT_NON) { + ((MyRendererTask)mRendererTask).setParams(effect, params); + } else { + throw new IllegalArgumentException("invalid effect number:" + effect); + } + } + + /** + * 内蔵映像効果以外のeffectを指定したときの処理 + * 描画用のワーカースレッド上で呼び出される + * このクラスでは無変換のフラグメントシェーダーを適用する + * @param effect + * @param drawer GLDrawer2Dインスタンス + */ + protected void handleDefaultEffect(final int effect, + @NonNull final IDrawer2dES2 drawer) { + + if (drawer instanceof GLDrawer2D) { + ((GLDrawer2D) drawer).resetShader(); + } + } + +//================================================================================ +// 実装 +//================================================================================ + private static final int REQUEST_CHANGE_EFFECT = 100; + private static final int REQUEST_SET_PARAMS = 101; + + /** + * ワーカースレッド上でOpenGL|ESを用いてマスター映像を分配描画するためのインナークラス + */ + protected static final class MyRendererTask extends RendererTask { + + private final SparseArray mParams = new SparseArray(); + private int muParamsLoc; + private float[] mCurrentParams; + private int mEffect; + + public MyRendererTask(final EffectRendererHolder parent, + final int width, final int height) { + + super(parent, width, height); + } + + public MyRendererTask(@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); + } + + /** + * ワーカースレッド開始時の処理(ここはワーカースレッド上) + */ + @SuppressLint("NewApi") + @Override + protected void internalOnStart() { +// if (DEBUG) Log.v(TAG, "onStart:"); + super.internalOnStart(); + mParams.clear(); + mParams.put(EFFECT_EMPHASIZE_RED_YELLOW, new float[] { + 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + 0.50f, 1.0f, // 強調する彩度下限, 上限 + 0.40f, 1.0f, // 強調する明度下限, 上限 + 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + 1.0f, 1.0f, 1.0f, // 通常時のファクター(H, S, Vの順) + }); + mParams.put(EFFECT_EMPHASIZE_RED_YELLOW_WHITE, new float[] { + 0.17f, 0.85f, // 赤色&黄色の色相下側閾値, 上側閾値 + 0.50f, 1.0f, // 強調する彩度下限, 上限 + 0.40f, 1.0f, // 強調する明度下限, 上限 + 1.0f, 1.0f, 5.0f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + 1.0f, 1.0f, 1.0f, // 通常時のファクター(H, S, Vの順) + }); + mParams.put(EFFECT_EMPHASIZE_YELLOW_WHITE, new float[] { + 0.10f, 0.19f, // 黄色の色相h下側閾値, 上側閾値 + 0.30f, 1.00f, // 強調する彩度s下限, 上限 + 0.30f, 1.00f, // 強調する明度v下限, 上限 + 1.00f, 1.00f, 5.00f, // 強調時のファクター(H, S, Vの順) 明度(x5.0) = 1.0 + 1.00f, 0.80f, 0.80f, // 通常時のファクター(H, S, Vの順) 彩度(x0.8)と明度(x0.8)を少し落とす + 0.15f, 0.40f, // 白強調時の彩度上限, 白強調時の明度下限 + 0, 0, 0, 0, // ダミー + }); + mEffect = EFFECT_NON; + handleChangeEffect(EFFECT_NON); +// if (DEBUG) Log.v(TAG, "onStart:finished"); + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) { + + Object result = null; + switch (request) { + case REQUEST_CHANGE_EFFECT: + handleChangeEffect(arg1); + break; + case REQUEST_SET_PARAMS: + handleSetParam(arg1, (float[])obj); + break; + default: + result = super.processRequest(request, arg1, arg2, obj); + break; + } + return result; + } + + public void changeEffect(final int effect) { + checkFinished(); + if (mEffect != effect) { + offer(REQUEST_CHANGE_EFFECT, effect); + } + } + + public void setParams(final int effect, @NonNull final float[] params) { + checkFinished(); + offer(REQUEST_SET_PARAMS, effect, 0, params); + } + +//================================================================================ +// ワーカースレッド上での処理 +//================================================================================ + /** + * 映像効果を変更 + * @param effect + */ + protected void handleChangeEffect(final int effect) { + mEffect = effect; + switch (effect) { + case EFFECT_NON: + mDrawer.updateShader(FRAGMENT_SHADER_SIMPLE_OES); + break; + case EFFECT_GRAY: + mDrawer.updateShader(FRAGMENT_SHADER_GRAY_OES); + break; + case EFFECT_GRAY_REVERSE: + mDrawer.updateShader(FRAGMENT_SHADER_GRAY_REVERSE_OES); + break; + case EFFECT_BIN: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_OES); + break; + case EFFECT_BIN_YELLOW: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_YELLOW_OES); + break; + case EFFECT_BIN_GREEN: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_GREEN_OES); + break; + case EFFECT_BIN_REVERSE: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_REVERSE_OES); + break; + case EFFECT_BIN_REVERSE_YELLOW: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_REVERSE_YELLOW_OES); + break; + case EFFECT_BIN_REVERSE_GREEN: + mDrawer.updateShader(FRAGMENT_SHADER_BIN_REVERSE_GREEN_OES); + break; + case EFFECT_EMPHASIZE_RED_YELLOW: + mDrawer.updateShader(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_OES); + break; + case EFFECT_EMPHASIZE_RED_YELLOW_WHITE: + mDrawer.updateShader(FRAGMENT_SHADER_EMPHASIZE_RED_YELLOW_WHITE_OES); + break; + case EFFECT_EMPHASIZE_YELLOW_WHITE: + mDrawer.updateShader(FRAGMENT_SHADER_EMPHASIZE_YELLOW_WHITE_OES); + break; + default: + try { + ((EffectRendererHolder)getParent()) + .handleDefaultEffect(effect, mDrawer); + } catch (final Exception e) { + mDrawer.resetShader(); + Log.w(TAG, e); + } + break; + } + muParamsLoc = mDrawer.glGetUniformLocation("uParams"); + mCurrentParams = mParams.get(effect); + updateParams(); + } + + /** + * 映像効果用のパラメーターをセット + * @param effect + * @param params + */ + private void handleSetParam(final int effect, @NonNull final float[] params) { + if ((effect < EFFECT_NON) || (mEffect == effect)) { + mCurrentParams = params; + mParams.put(mEffect, params); + updateParams(); + } else { + mParams.put(effect, params); + } + } + + /** + * 映像効果用のパラメータをGPUへ適用 + */ + private void updateParams() { + final int n = Math.min(mCurrentParams != null + ? mCurrentParams.length : 0, MAX_PARAM_NUM); + if ((muParamsLoc >= 0) && (n > 0)) { + mDrawer.glUseProgram(); + GLES20.glUniform1fv(muParamsLoc, n, mCurrentParams, 0); + } + } + + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EglTask.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EglTask.java new file mode 100644 index 0000000000..1f36e6348d --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/EglTask.java @@ -0,0 +1,122 @@ +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 androidx.annotation.Nullable; + +import com.serenegiant.utils.MessageTask; + +public abstract class EglTask extends MessageTask { +// private static final boolean DEBUG = false; +// private static final String TAG = "EglTask"; + + public static final int EGL_FLAG_DEPTH_BUFFER = 0x01; + public static final int EGL_FLAG_RECORDABLE = 0x02; + public static final int EGL_FLAG_STENCIL_1BIT = 0x04; +// public static final int EGL_FLAG_STENCIL_2BIT = 0x08; +// public static final int EGL_FLAG_STENCIL_4BIT = 0x10; + public static final int EGL_FLAG_STENCIL_8BIT = 0x20; + + private EGLBase mEgl = null; + private EGLBase.IEglSurface mEglHolder; + + public EglTask(final EGLBase.IContext sharedContext, final int flags) { +// if (DEBUG) Log.i(TAG, "shared_context=" + shared_context); + init(flags, 3, sharedContext); + } + + public EglTask(final int maxClientVersion, + final EGLBase.IContext sharedContext, final int flags) { + +// if (DEBUG) Log.i(TAG, "shared_context=" + shared_context); + init(flags, maxClientVersion, sharedContext); + } + + /** + * @param flags + * @param maxClientVersion + * @param sharedContext + */ + @Override + protected void onInit(final int flags, + final int maxClientVersion, final Object sharedContext) { + + if ((sharedContext == null) + || (sharedContext instanceof EGLBase.IContext)) { + + final int stencilBits + = (flags & EGL_FLAG_STENCIL_1BIT) == EGL_FLAG_STENCIL_1BIT ? 1 + : ((flags & EGL_FLAG_STENCIL_8BIT) == EGL_FLAG_STENCIL_8BIT ? 8 : 0); + mEgl = EGLBase.createFrom(maxClientVersion, (EGLBase.IContext)sharedContext, + (flags & EGL_FLAG_DEPTH_BUFFER) == EGL_FLAG_DEPTH_BUFFER, + stencilBits, + (flags & EGL_FLAG_RECORDABLE) == EGL_FLAG_RECORDABLE); + } + if (mEgl == null) { + callOnError(new RuntimeException("failed to create EglCore")); + releaseSelf(); + } else { + mEglHolder = mEgl.createOffscreen(1, 1); + mEglHolder.makeCurrent(); + } + } + + @Override + protected Request takeRequest() throws InterruptedException { + final Request result = super.takeRequest(); + mEglHolder.makeCurrent(); + return result; + } + + @Override + protected void onBeforeStop() { + mEglHolder.makeCurrent(); + } + + @Override + protected void onRelease() { + mEglHolder.release(); + mEgl.release(); + } + + protected EGLBase getEgl() { + return mEgl; + } + + protected EGLBase.IContext getEGLContext() { + return mEgl.getContext(); + } + + protected EGLBase.IConfig getConfig() { + return mEgl.getConfig(); + } + + @Nullable + protected EGLBase.IContext getContext() { + return mEgl != null ? mEgl.getContext() : null; + } + + protected void makeCurrent() { + mEglHolder.makeCurrent(); + } + + protected boolean isGLES3() { + return (mEgl != null) && (mEgl.getGlVersion() > 2); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLDrawer2D.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLDrawer2D.java new file mode 100644 index 0000000000..1d73f13a7d --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLDrawer2D.java @@ -0,0 +1,307 @@ +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.FRAGMENT_SHADER_SIMPLE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_SIMPLE_OES; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_2D; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; +import static com.serenegiant.glutils.ShaderConst.VERTEX_SHADER; + +import android.opengl.GLES20; +import android.opengl.Matrix; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * 描画領域全面にテクスチャを2D描画するためのヘルパークラス + */ +public class GLDrawer2D implements IDrawer2dES2 { +// private static final boolean DEBUG = false; // FIXME set false on release +// private static final String TAG = "GLDrawer2D"; + + private static final float[] VERTICES = { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; + private static final float[] TEXCOORD = { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + private static final int FLOAT_SZ = Float.SIZE / 8; + + private final int VERTEX_NUM; + private final int VERTEX_SZ; + private final FloatBuffer pVertex; + private final FloatBuffer pTexCoord; + private final int mTexTarget; + private int hProgram; + int maPositionLoc; + int maTextureCoordLoc; + int muMVPMatrixLoc; + int muTexMatrixLoc; + private final float[] mMvpMatrix = new float[16]; + + /** + * コンストラクタ + * GLコンテキスト/EGLレンダリングコンテキストが有効な状態で呼ばないとダメ + * @param isOES 外部テクスチャ(GL_TEXTURE_EXTERNAL_OES)を使う場合はtrue。 + * 通常の2Dテキスチャならfalse + */ + public GLDrawer2D(final boolean isOES) { + this(VERTICES, TEXCOORD, isOES); + } + + /** + * コンストラクタ + * GLコンテキスト/EGLレンダリングコンテキストが有効な状態で呼ばないとダメ + * @param vertices 頂点座標, floatを8個 = (x,y) x 4ペア + * @param texcoord テクスチャ座標, floatを8個 = (s,t) x 4ペア + * @param isOES 外部テクスチャ(GL_TEXTURE_EXTERNAL_OES)を使う場合はtrue。 + * 通常の2Dテキスチャならfalse + */ + public GLDrawer2D(final float[] vertices, + final float[] texcoord, final boolean isOES) { + + VERTEX_NUM = Math.min( + vertices != null ? vertices.length : 0, + texcoord != null ? texcoord.length : 0) / 2; + VERTEX_SZ = VERTEX_NUM * 2; + + mTexTarget = isOES ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + pVertex = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pVertex.put(vertices); + pVertex.flip(); + pTexCoord = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pTexCoord.put(texcoord); + pTexCoord.flip(); + + if (isOES) { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE_OES); + } else { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE); + } + // モデルビュー変換行列を初期化 + Matrix.setIdentityM(mMvpMatrix, 0); + init(); + } + + /** + * 破棄処理。GLコンテキスト/EGLレンダリングコンテキスト内で呼び出さないとダメ + */ + @Override + public void release() { + if (hProgram >= 0) { + GLES20.glDeleteProgram(hProgram); + } + hProgram = -1; + } + + /** + * 外部テクスチャを使うかどうか + * @return + */ + public boolean isOES() { + return mTexTarget == GL_TEXTURE_EXTERNAL_OES; + } + + /** + * モデルビュー変換行列を取得(内部配列を直接返すので変更時は要注意) + * @return + */ + @Override + public float[] getMvpMatrix() { + return mMvpMatrix; + } + + /** + * モデルビュー変換行列に行列を割り当てる + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + * @return + */ + @Override + public IDrawer2D setMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(matrix, offset, mMvpMatrix, 0, 16); + return this; + } + + /** + * モデルビュー変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + */ + @Override + public void getMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(mMvpMatrix, 0, matrix, offset, 16); + } + + /** + * 指定したテクスチャを指定したテクスチャ変換行列を使って描画領域全面に描画するためのヘルパーメソッド + * このクラスインスタンスのモデルビュー変換行列が設定されていればそれも適用された状態で描画する + * @param texId texture ID + * @param tex_matrix テクスチャ変換行列、nullならば以前に適用したものが再利用される。 + * 領域チェックしていないのでoffsetから16個以上確保しておくこと + */ + @Override + public synchronized void draw(final int texId, + final float[] tex_matrix, final int offset) { + +// if (DEBUG) Log.v(TAG, "draw"); + if (hProgram < 0) return; + GLES20.glUseProgram(hProgram); + if (tex_matrix != null) { + // テクスチャ変換行列が指定されている時 + GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, tex_matrix, offset); + } + // モデルビュー変換行列をセット + GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mMvpMatrix, 0); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(mTexTarget, texId); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_NUM); + GLES20.glBindTexture(mTexTarget, 0); + GLES20.glUseProgram(0); + } + + /** + * Textureオブジェクトを描画するためのヘルパーメソッド + * Textureオブジェクトで管理しているテクスチャ名とテクスチャ座標変換行列を使って描画する + * @param texture + */ + @Override + public void draw(final ITexture texture) { + draw(texture.getTexture(), texture.getTexMatrix(), 0); + } + + /** + * TextureOffscreenオブジェクトを描画するためのヘルパーメソッド + * @param offscreen + */ + @Override + public void draw(final TextureOffscreen offscreen) { + draw(offscreen.getTexture(), offscreen.getTexMatrix(), 0); + } + + /** + * テクスチャ名生成のヘルパーメソッド + * GLHelper#initTexを呼び出すだけ + * @return texture ID + */ + public int initTex() { + return GLHelper.initTex(mTexTarget, GLES20.GL_NEAREST); + } + + /** + * テクスチャ名破棄のヘルパーメソッド + * GLHelper.deleteTexを呼び出すだけ + * @param hTex + */ + public void deleteTex(final int hTex) { + GLHelper.deleteTex(hTex); + } + + /** + * 頂点シェーダー・フラグメントシェーダーを変更する + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出さないとダメ + * glUseProgramが呼ばれた状態で返る + * @param vs 頂点シェーダー文字列 + * @param fs フラグメントシェーダー文字列 + */ + public synchronized void updateShader(final String vs, final String fs) { + release(); + hProgram = GLHelper.loadShader(vs, fs); + init(); + } + + /** + * フラグメントシェーダーを変更する + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出さないとダメ + * glUseProgramが呼ばれた状態で返る + * @param fs フラグメントシェーダー文字列 + */ + public void updateShader(final String fs) { + updateShader(VERTEX_SHADER, fs); + } + + /** + * 頂点シェーダー・フラグメントシェーダーをデフォルトに戻す + */ + public void resetShader() { + release(); + if (isOES()) { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE_OES); + } else { + hProgram = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SIMPLE); + } + init(); + } + + /** + * アトリビュート変数のロケーションを取得 + * glUseProgramが呼ばれた状態で返る + * @param name + * @return + */ + @Override + public int glGetAttribLocation(final String name) { + GLES20.glUseProgram(hProgram); + return GLES20.glGetAttribLocation(hProgram, name); + } + + /** + * ユニフォーム変数のロケーションを取得 + * glUseProgramが呼ばれた状態で返る + * @param name + * @return + */ + @Override + public int glGetUniformLocation(final String name) { + GLES20.glUseProgram(hProgram); + return GLES20.glGetUniformLocation(hProgram, name); + } + + /** + * glUseProgramが呼ばれた状態で返る + */ + @Override + public void glUseProgram() { + GLES20.glUseProgram(hProgram); + } + + /** + * シェーダープログラム変更時の初期化処理 + * glUseProgramが呼ばれた状態で返る + */ + private void init() { + GLES20.glUseProgram(hProgram); + maPositionLoc = GLES20.glGetAttribLocation(hProgram, "aPosition"); + maTextureCoordLoc = GLES20.glGetAttribLocation(hProgram, "aTextureCoord"); + muMVPMatrixLoc = GLES20.glGetUniformLocation(hProgram, "uMVPMatrix"); + muTexMatrixLoc = GLES20.glGetUniformLocation(hProgram, "uTexMatrix"); + // + GLES20.glUniformMatrix4fv(muMVPMatrixLoc, + 1, false, mMvpMatrix, 0); + GLES20.glUniformMatrix4fv(muTexMatrixLoc, + 1, false, mMvpMatrix, 0); + GLES20.glVertexAttribPointer(maPositionLoc, + 2, GLES20.GL_FLOAT, false, VERTEX_SZ, pVertex); + GLES20.glVertexAttribPointer(maTextureCoordLoc, + 2, GLES20.GL_FLOAT, false, VERTEX_SZ, pTexCoord); + GLES20.glEnableVertexAttribArray(maPositionLoc); + GLES20.glEnableVertexAttribArray(maTextureCoordLoc); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLHelper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLHelper.java new file mode 100644 index 0000000000..c4c8adfd72 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLHelper.java @@ -0,0 +1,417 @@ +package com.serenegiant.glutils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.opengl.GLES20; +import android.opengl.GLES30; +import android.opengl.GLUtils; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.serenegiant.utils.AssetsHelper; +import com.serenegiant.utils.BuildCheck; + +import java.io.IOException; + +/** + * OpenGL|ES2/3用のヘルパークラス + */ +public final class GLHelper { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = "GLHelper"; + + /** + * OpenGL|ESのエラーをチェックしてlogCatに出力する + * @param op + */ + public static void checkGlError(final String op) { + final int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR) { + final String msg = op + ": glError 0x" + Integer.toHexString(error); + Log.e(TAG, msg); + new Throwable(msg).printStackTrace(); +// if (DEBUG) { +// throw new RuntimeException(msg); +// } + } + } + + /** + * テクスチャ名を生成, テクスチャユニットはGL_TEXTURE0, クランプ方法はGL_CLAMP_TO_EDGE + * @param texTarget + * @param filter_param テクスチャの補完方法を指定, min/mag共に同じ値になる, GL_LINEARとかGL_NEAREST + * @return + */ + public static int initTex(final int texTarget, final int filter_param) { + return initTex(texTarget, GLES20.GL_TEXTURE0, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名を生成(GL_TEXTURE0のみ) + * @param texTarget + * @param texUnit テクスチャユニット, GL_TEXTURE0...GL_TEXTURE31 + * @param min_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param mag_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param wrap テクスチャのクランプ方法, GL_CLAMP_TO_EDGE + * @return + */ + public static int initTex(final int texTarget, final int texUnit, + final int min_filter, final int mag_filter, final int wrap) { + +// if (DEBUG) Log.v(TAG, "initTex:target=" + texTarget); + final int[] tex = new int[1]; + GLES20.glActiveTexture(texUnit); + GLES20.glGenTextures(1, tex, 0); + GLES20.glBindTexture(texTarget, tex[0]); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_WRAP_S, wrap); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_WRAP_T, wrap); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_MIN_FILTER, min_filter); + GLES20.glTexParameteri(texTarget, GLES20.GL_TEXTURE_MAG_FILTER, mag_filter); + return tex[0]; + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param n 生成するテキスチャ名の数, 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param filter_param + * @return + */ + public static int[] initTexes(final int n, + final int texTarget, final int filter_param) { + + return initTexes(new int[n], texTarget, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param texIds テクスチャ名配列, 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param filter_param + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int filter_param) { + + return initTexes(texIds, texTarget, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param n 生成するテキスチャ名の数, 最大32 + * @param texTarget + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(final int n, + final int texTarget, final int min_filter, final int mag_filter, final int wrap) { + + return initTexes(new int[n], texTarget, min_filter, mag_filter, wrap); + } + + /** + * テクスチャ名配列を生成(前から順にGL_TEXTURE0, GL_TEXTURE1, ...) + * @param texIds テクスチャ名配列, 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int min_filter, final int mag_filter, final int wrap) { + + int[] textureUnits = new int[1]; + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, textureUnits, 0); + Log.v(TAG, "GL_MAX_TEXTURE_IMAGE_UNITS=" + textureUnits[0]); + final int n = texIds.length > textureUnits[0] + ? textureUnits[0] : texIds.length; + for (int i = 0; i < n; i++) { + texIds[i] = GLHelper.initTex(texTarget, ShaderConst.TEX_NUMBERS[i], + min_filter, mag_filter, wrap); + } + return texIds; + } + + /** + * テクスチャ名配列を生成(こっちは全部同じテクスチャユニット) + * @param n 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param texUnit + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(final int n, + final int texTarget, final int texUnit, + final int min_filter, final int mag_filter, final int wrap) { + + return initTexes(new int[n], texTarget, texUnit, + min_filter, mag_filter, wrap); + } + + /** + * テクスチャ名配列を生成(こっちは全部同じテクスチャユニット) + * @param texIds 最大で32個(GL_MAX_TEXTURE_IMAGE_UNITS以下) + * @param texTarget + * @param texUnit + * @param filter_param + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int texUnit, final int filter_param) { + + return initTexes(texIds, texTarget, texUnit, + filter_param, filter_param, GLES20.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名配列を生成(こっちは全部同じテクスチャユニット) + * @param texIds + * @param texTarget + * @param texUnit + * @param min_filter + * @param mag_filter + * @param wrap + * @return + */ + public static int[] initTexes(@NonNull final int[] texIds, + final int texTarget, final int texUnit, + final int min_filter, final int mag_filter, final int wrap) { + + int[] textureUnits = new int[1]; + GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, textureUnits, 0); + final int n = texIds.length > textureUnits[0] + ? textureUnits[0] : texIds.length; + for (int i = 0; i < n; i++) { + texIds[i] = GLHelper.initTex(texTarget, texUnit, + min_filter, mag_filter, wrap); + } + return texIds; + } + + /** + * delete specific texture + */ + public static void deleteTex(final int hTex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + final int[] tex = new int[] {hTex}; + GLES20.glDeleteTextures(1, tex, 0); + } + + /** + * delete specific texture + */ + public static void deleteTex(@NonNull final int[] tex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + GLES20.glDeleteTextures(tex.length, tex, 0); + } + + public static int loadTextureFromResource(final Context context, final int resId) { + return loadTextureFromResource(context, resId, null); + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public static int loadTextureFromResource(final Context context, final int resId, final Resources.Theme theme) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // get a background image from resources + // note the image format must match the bitmap format + final Drawable background; + if (BuildCheck.isAndroid5()) { + background = context.getResources().getDrawable(resId, theme); + } else { + background = context.getResources().getDrawable(resId); + } + background.setBounds(0, 0, 256, 256); + background.draw(canvas); // draw the background to our bitmap + + final int[] textures = new int[1]; + + //Generate one texture pointer... + GLES20.glGenTextures(1, textures, 0); + //...and bind it to our array + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + + //Create Nearest Filtered Texture + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + + //Different possible texture parameters, e.g. GLES20.GL_CLAMP_TO_EDGE + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT); + + //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + //Clean up + bitmap.recycle(); + + return textures[0]; + } + + public static int createTextureWithTextContent (final String text) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // Draw the text + final Paint textPaint = new Paint(); + textPaint.setTextSize(32); + textPaint.setAntiAlias(true); + textPaint.setARGB(0xff, 0xff, 0xff, 0xff); + // draw the text centered + canvas.drawText(text, 16, 112, textPaint); + + final int texture = initTex(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE0, GLES20.GL_NEAREST, GLES20.GL_LINEAR, GLES20.GL_REPEAT); + + // Alpha blending + // GLES20.glEnable(GLES20.GL_BLEND); + // GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + + // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + // Clean up + bitmap.recycle(); + + return texture; + } + + /** + * load, compile and link shader from Assets files + * @param context + * @param vss_asset source file name in Assets of vertex shader + * @param fss_asset source file name in Assets of fragment shader + * @return + */ + public static int loadShader(@NonNull final Context context, + final String vss_asset, final String fss_asset) { + + int program = 0; + try { + final String vss = AssetsHelper.loadString(context.getAssets(), vss_asset); + final String fss = AssetsHelper.loadString(context.getAssets(), vss_asset); + program = loadShader(vss, fss); + } catch (IOException e) { + } + return program; + } + + /** + * load, compile and link shader + * @param vss source of vertex shader + * @param fss source of fragment shader + * @return + */ + public static int loadShader(final String vss, final String fss) { +// if (DEBUG) Log.v(TAG, "loadShader:"); + final int[] compiled = new int[1]; + // 頂点シェーダーをコンパイル + final int vs = loadShader(GLES20.GL_VERTEX_SHADER, vss); + if (vs == 0) { + return 0; + } + // フラグメントシェーダーをコンパイル + int fs = loadShader(GLES20.GL_FRAGMENT_SHADER, fss); + if (fs == 0) { + return 0; + } + // リンク + final int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + Log.e(TAG, "Could not create program"); + } + GLES20.glAttachShader(program, vs); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, fs); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + final int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + return 0; + } + return program; + } + + /** + * Compiles the provided shader source. + * + * @return A handle to the shader, or 0 on failure. + */ + public static int loadShader(final int shaderType, final String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + final int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + /** + * Checks to see if the location we obtained is valid. GLES returns -1 if a label + * could not be found, but does not set the GL error. + *

+ * Throws a RuntimeException if the location is invalid. + */ + public static void checkLocation(final int location, final String label) { + if (location < 0) { + throw new RuntimeException("Unable to locate '" + label + "' in program"); + } + } + + /** + * Writes GL version info to the log. + */ + @SuppressLint("InlinedApi") + public static void logVersionInfo() { + Log.i(TAG, "vendor : " + GLES20.glGetString(GLES20.GL_VENDOR)); + Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER)); + Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION)); + + if (BuildCheck.isAndroid4_3()) { + final int[] values = new int[1]; + GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); + final int majorVersion = values[0]; + GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); + final int minorVersion = values[0]; + if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { + Log.i(TAG, "version: " + majorVersion + "." + minorVersion); + } + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLMasterContext.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLMasterContext.java new file mode 100644 index 0000000000..eb1616f031 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLMasterContext.java @@ -0,0 +1,66 @@ +package com.serenegiant.glutils; + +/** + * Created by saki on 2018/02/10. + * 共有コンテキストのマスターをを保持するためだけのクラス + * Applicationクラス等でシングルトンとして使う + */ +public class GLMasterContext { + private static final String TAG = GLMasterContext.class.getSimpleName(); + + private MasterTask mMasterTask; + + public GLMasterContext(final int maxClientVersion, final int flags) { + mMasterTask = new MasterTask(maxClientVersion, flags); + new Thread(mMasterTask, TAG).start(); + mMasterTask.waitReady(); + } + + @Override + protected void finalize() throws Throwable { + try { + release(); + } finally { + super.finalize(); + } + } + + public synchronized void release() { + if (mMasterTask != null) { + mMasterTask.release(); + mMasterTask = null; + } + } + + public synchronized EGLBase.IContext getContext() + throws IllegalStateException { + if (mMasterTask != null) { + return mMasterTask.getContext(); + } else { + throw new IllegalStateException("already released"); + } + } + + private static class MasterTask extends EglTask { + public MasterTask(final int maxClientVersion, final int flags) { + super(maxClientVersion, null, flags); + } + + @Override + protected void onStart() { + // do nothing + } + + @Override + protected void onStop() { + // do nothing + } + + @Override + protected Object processRequest(final int request, + final int arg1, final int arg2, final Object obj) throws TaskBreak { + // do nothing + return null; + } + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLTexture.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLTexture.java new file mode 100644 index 0000000000..9f08ad3abc --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/GLTexture.java @@ -0,0 +1,234 @@ +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 android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.text.TextUtils; + +import java.io.IOException; + +/** + * OpenGL|ESのテクスチャ操作用のヘルパークラス + */ +public class GLTexture implements ITexture { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること +// private static final String TAG = "GLTexture"; + + /* package */final int mTextureTarget; + /* package */final int mTextureUnit ; + /* package */int mTextureId; + /* package */final float[] mTexMatrix = new float[16]; // テクスチャ変換行列 + /* package */int mTexWidth, mTexHeight; + /* package */int mImageWidth, mImageHeight; + + /** + * コンストラクタ + * テクスチャユニットが常時GL_TEXTURE0なので複数のテクスチャを同時に使えない + * @param width テクスチャサイズ + * @param height テクスチャサイズ + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + */ + public GLTexture(final int width, final int height, final int filter_param) { + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, width, height, filter_param); + } + + /** + * コンストラクタ + * @param texTarget GL_TEXTURE_EXTERNAL_OESはだめ + * @param texUnit + * @param width テクスチャサイズ + * @param height テクスチャサイズ + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + */ + public GLTexture(final int texTarget, final int texUnit, + final int width, final int height, final int filter_param) { +// if (DEBUG) Log.v(TAG, String.format("コンストラクタ(%d,%d)", width, height)); + mTextureTarget = texTarget; + mTextureUnit = texUnit; + // テクスチャに使うビットマップは縦横サイズが2の乗数でないとダメ。 + // 更に、ミップマップするなら正方形でないとダメ + // 指定したwidth/heightと同じか大きい2の乗数にする + int w = 32; + for (; w < width; w <<= 1); + int h = 32; + for (; h < height; h <<= 1); + if (mTexWidth != w || mTexHeight != h) { + mTexWidth = w; + mTexHeight = h; + } +// if (DEBUG) Log.v(TAG, String.format("texSize(%d,%d)", mTexWidth, mTexHeight)); + mTextureId = GLHelper.initTex(mTextureTarget, filter_param); + // テクスチャのメモリ領域を確保する + GLES20.glTexImage2D(mTextureTarget, + 0, // ミップマップレベル0(ミップマップしない) + GLES20.GL_RGBA, // 内部フォーマット + mTexWidth, mTexHeight, // サイズ + 0, // 境界幅 + GLES20.GL_RGBA, // 引き渡すデータのフォーマット + GLES20.GL_UNSIGNED_BYTE, // データの型 + null); // ピクセルデータ無し + // テクスチャ変換行列を初期化 + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, "GLTexture:id=" + mTextureId); + } + + @Override + protected void finalize() throws Throwable { + try { + release(); // GLコンテキスト内じゃない可能性があるのであまり良くないけど + } finally { + super.finalize(); + } + } + + /** + * テクスチャを破棄 + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出すこと + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + if (mTextureId > 0) { + GLHelper.deleteTex(mTextureId); + mTextureId = 0; + } + } + + /** + * このインスタンスで管理しているテクスチャを有効にする(バインドする) + */ + @Override + public void bind() { +// if (DEBUG) Log.v(TAG, "bind:"); + GLES20.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES20.glBindTexture(mTextureTarget, mTextureId); + } + + /** + * このインスタンスで管理しているテクスチャを無効にする(アンバインドする) + */ + @Override + public void unbind() { +// if (DEBUG) Log.v(TAG, "unbind:"); + GLES20.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES20.glBindTexture(mTextureTarget, 0); + } + + /** + * テクスチャターゲットを取得(GL_TEXTURE_2D) + * @return + */ + @Override + public int getTexTarget() { return mTextureTarget; } + /** + * テクスチャ名を取得 + * @return + */ + @Override + public int getTexture() { return mTextureId; } + /** + * テクスチャ座標変換行列を取得(内部配列をそのまま返すので変更時は要注意) + * @return + */ + @Override + public float[] getTexMatrix() { return mTexMatrix; } + /** + * テクスチャ座標変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffset位置から16個以上確保しておくこと + * @param offset + */ + @Override + public void getTexMatrix(final float[] matrix, final int offset) { + System.arraycopy(mTexMatrix, 0, matrix, offset, mTexMatrix.length); + } + /** + * テクスチャ幅を取得 + * @return + */ + @Override + public int getTexWidth() { return mTexWidth; } + /** + * テクスチャ高さを取得 + * @return + */ + @Override + public int getTexHeight() { return mTexHeight; } + + /** + * 指定したファイルから画像をテクスチャに読み込む + * ファイルが存在しないか読み込めなければIOException/NullPointerExceptionを生成 + * @param filePath + */ + @Override + public void loadTexture(final String filePath) throws NullPointerException, IOException { +// if (DEBUG) Log.v(TAG, "loadTexture:path=" + filePath); + if (TextUtils.isEmpty(filePath)) + throw new NullPointerException("image file path should not be a null"); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // Bitmapを生成せずにサイズ等の情報だけを取得する + BitmapFactory.decodeFile(filePath, options); + // テキスチャサイズ内に指定したイメージが収まるためのサブサンプリングを値を求める + final int imageWidth = options.outWidth; + final int imageHeight = options.outHeight; + int inSampleSize = 1; // サブサンプリングサイズ + if ((imageHeight > mTexHeight) || (imageWidth > mTexWidth)) { + if (imageWidth > imageHeight) { + inSampleSize = (int)Math.ceil(imageHeight / (float)mTexHeight); + } else { + inSampleSize = (int)Math.ceil(imageWidth / (float)mTexWidth); + } + } +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),tex(%d,%d),inSampleSize=%d", +// imageWidth, imageHeight, mTexWidth, mTexHeight, inSampleSize)); + // 実際の読み込み処理 + options.inSampleSize = inSampleSize; + options.inJustDecodeBounds = false; + loadTexture(BitmapFactory.decodeFile(filePath, options)); + } + + /** + * 指定したビットマップをテクスチャに読み込む + * @param bitmap + */ + public void loadTexture(final Bitmap bitmap) throws NullPointerException { + mImageWidth = bitmap.getWidth(); // 読み込んだイメージのサイズを取得 + mImageHeight = bitmap.getHeight(); + Bitmap texture = Bitmap.createBitmap(mTexWidth, mTexHeight, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(texture); + canvas.drawBitmap(bitmap, 0, 0, null); + bitmap.recycle(); + // テクスチャ座標変換行列を設定(読み込んだイメージサイズがテクスチャサイズにフィットするようにスケール変換) + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = mImageWidth / (float)mTexWidth; + mTexMatrix[5] = mImageHeight / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),scale(%f,%f)", +// mImageWidth, mImageHeight, mMvpMatrix[0], mMvpMatrix[5])); + bind(); + GLUtils.texImage2D(mTextureTarget, 0, texture, 0); + unbind(); + texture.recycle(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2D.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2D.java new file mode 100644 index 0000000000..d7c4ce816e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2D.java @@ -0,0 +1,29 @@ +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. +*/ + +public interface IDrawer2D { + public void release(); + public float[] getMvpMatrix(); + public IDrawer2D setMvpMatrix(final float[] matrix, final int offset); + public void getMvpMatrix(final float[] matrix, final int offset); + public void draw(final int texId, final float[] tex_matrix, final int offset); + public void draw(final ITexture texture); + public void draw(final TextureOffscreen offscreen); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2dES2.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2dES2.java new file mode 100644 index 0000000000..9548cb941f --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IDrawer2dES2.java @@ -0,0 +1,25 @@ +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. +*/ + +public interface IDrawer2dES2 extends IDrawer2D { + public int glGetAttribLocation(final String name); + public int glGetUniformLocation(final String name); + public void glUseProgram(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRenderer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRenderer.java new file mode 100644 index 0000000000..b13e45d389 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRenderer.java @@ -0,0 +1,55 @@ +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 android.graphics.SurfaceTexture; +import android.view.Surface; + +public interface IRenderer extends IRendererCommon { + + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + public void release(); + + /** + * 描画先のSurfaceをセット + * @param surface + */ + public void setSurface(final Surface surface); + + /** + * 描画先のSurfaceをセット + * @param surface + */ + public void setSurface(final SurfaceTexture surface); + + /** + * Surfaceサイズを変更 + * @param width + * @param height + */ + public void resize(final int width, final int height); + + /** + * 描画要求 + * @param args + */ + public void requestRender(final Object... args); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererCommon.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererCommon.java new file mode 100644 index 0000000000..f7e080595e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererCommon.java @@ -0,0 +1,48 @@ +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 androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public interface IRendererCommon { + public static final int MIRROR_NORMAL = 0; + public static final int MIRROR_HORIZONTAL = 1; + public static final int MIRROR_VERTICAL = 2; + public static final int MIRROR_BOTH = 3; + public static final int MIRROR_NUM = 4; + + @IntDef({MIRROR_NORMAL, MIRROR_HORIZONTAL, MIRROR_VERTICAL, MIRROR_BOTH}) + @Retention(RetentionPolicy.SOURCE) + public @interface MirrorMode {} + + /** + * 映像を上下左右反転させるかどうかをセット + * @param mirror 0:通常, 1:左右反転, 2:上下反転, 3:上下左右反転 + */ + public void setMirror(@MirrorMode final int mirror); + + /** + * 映像を上下左右反転させるかどうかを取得 + * @return 0:通常, 1:左右反転, 2:上下反転, 3:上下左右反転 + */ + public @MirrorMode int getMirror(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererHolder.java new file mode 100644 index 0000000000..9120cde2f5 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/IRendererHolder.java @@ -0,0 +1,223 @@ +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 android.graphics.SurfaceTexture; +import android.view.Surface; + +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.FileNotFoundException; +import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 分配描画インターフェース + */ +public interface IRendererHolder extends IRendererCommon { + public static final int DEFAULT_CAPTURE_COMPRESSION = 80; + + public static final int OUTPUT_FORMAT_JPEG = 0; // Bitmap.CompressFormat.JPEG + public static final int OUTPUT_FORMAT_PNG = 1; // Bitmap.CompressFormat.PNG + public static final int OUTPUT_FORMAT_WEBP = 2; // Bitmap.CompressFormat.WEBP + + @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_PNG, OUTPUT_FORMAT_WEBP}) + @Retention(RetentionPolicy.SOURCE) + public @interface StillCaptureFormat {} + + /** + * 実行中かどうか + * @return + */ + public boolean isRunning(); + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + public void release(); + + @Nullable + public EGLBase.IContext getContext(); + + /** + * マスター用の映像を受け取るためのSurfaceを取得 + * @return + */ + public Surface getSurface(); + + /** + * マスター用の映像を受け取るためのSurfaceTextureを取得 + * @return + */ + public SurfaceTexture getSurfaceTexture(); + + /** + * マスター用の映像を受け取るためのマスターをチェックして無効なら再生成要求する + */ + public void reset(); + + /** + * マスター映像サイズをサイズ変更要求 + * @param width + * @param height + */ + public void resize(final int width, final int height) + throws IllegalStateException; + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id 普通は#hashCodeを使う + * @param surface, should be one of Surface, SurfaceTexture or SurfaceHolder + * @param isRecordable + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable) + throws IllegalStateException, IllegalArgumentException; + + /** + * 分配描画用のSurfaceを追加 + * このメソッドは指定したSurfaceが追加されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id 普通は#hashCodeを使う + * @param surface, should be one of Surface, SurfaceTexture or SurfaceHolder + * @param isRecordable + * @param maxFps 0以下なら制限しない + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable, final int maxFps) + throws IllegalStateException, IllegalArgumentException; + + /** + * 分配描画用のSurfaceを削除 + * このメソッドは指定したSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + * @param id + */ + public void removeSurface(final int id); + + /** + * 分配描画用のSurfaceを全て削除 + * このメソッドはSurfaceが削除されるか + * interruptされるまでカレントスレッドをブロックする。 + */ + public void removeSurfaceAll(); + + /** + * 分配描画用のSurfaceを指定した色で塗りつぶす + * @param id + * @param color + */ + public void clearSurface(final int id, final int color); + + /** + * 分配描画用のSurfaceを指定した色で塗りつぶす + * @param color + */ + public void clearSurfaceAll(final int color); + + /** + * モデルビュー変換行列をセット + * @param id + * @param offset + * @param matrix offset以降に16要素以上 + */ + public void setMvpMatrix(final int id, + final int offset, @NonNull final float[] matrix); + + /** + * 分配描画用のSurfaceへの描画が有効かどうかを取得 + * @param id + * @return + */ + public boolean isEnabled(final int id); + + /** + * 分配描画用のSurfaceへの描画の有効・無効を切替 + * @param id + * @param enable + */ + public void setEnabled(final int id, final boolean enable); + + /** + * 強制的に現在の最新のフレームを描画要求する + * 分配描画用Surface全てが更新されるので注意 + */ + public void requestFrame(); + + /** + * 追加されている分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount(); + + /** + * 静止画を撮影する + * 撮影完了を待機しない + * @param path + */ + @Deprecated + public void captureStillAsync(@NonNull final String path) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機しない + * @param path + * @param captureCompression JPEGの圧縮率, pngの時は無視 + */ + @Deprecated + public void captureStillAsync(@NonNull final String path, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param path + */ + public void captureStill(@NonNull final String path) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param path + * @param captureCompression JPEGの圧縮率, pngの時は無視 + */ + public void captureStill(@NonNull final String path, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws FileNotFoundException, IllegalStateException; + + /** + * 静止画を撮影する + * 撮影完了を待機する + * @param out + * @param stillCaptureFormat + * @param captureCompression + */ + public void captureStill(@NonNull final OutputStream out, + @StillCaptureFormat final int stillCaptureFormat, + @IntRange(from = 1L,to = 99L) final int captureCompression) + throws IllegalStateException; +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ITexture.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ITexture.java new file mode 100644 index 0000000000..354b955245 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ITexture.java @@ -0,0 +1,42 @@ +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 android.graphics.Bitmap; + +import java.io.IOException; + +public interface ITexture { + void release(); + + void bind(); + void unbind(); + + int getTexTarget(); + int getTexture(); + + float[] getTexMatrix(); + void getTexMatrix(float[] matrix, int offset); + + int getTexWidth(); + int getTexHeight(); + + void loadTexture(String filePath) throws NullPointerException, IOException; + void loadTexture(Bitmap bitmap) throws NullPointerException; +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHandler.java new file mode 100644 index 0000000000..c88e9fb27e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHandler.java @@ -0,0 +1,266 @@ +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 android.graphics.SurfaceTexture; +import android.opengl.GLES20; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +/** + * Draw shared texture on specific whole Surface using OpenGL|ES + * this will deprecate soon because I don't use this now + */ +@Deprecated +public final class RenderHandler extends Handler { +// private static final boolean DEBUG = false; // FIXME set false on release + private static final String TAG = "RenderHandler"; + + private static final int MSG_RENDER_SET_GLCONTEXT = 1; + private static final int MSG_RENDER_DRAW = 2; + private static final int MSG_CHECK_VALID = 3; + private static final int MSG_RENDER_QUIT = 9; + + private int mTexId = -1; + private final RenderThread mThread; + + public static RenderHandler createHandler() { +// if (DEBUG) Log.v(TAG, "createHandler:"); + return createHandler("RenderThread"); + } + + public static final RenderHandler createHandler(final String name) { +// if (DEBUG) Log.v(TAG, "createHandler:name=" + name); + final RenderThread thread = new RenderThread(name); + thread.start(); + return thread.getHandler(); + } + + public final void setEglContext(final EGLBase.IContext sharedContext, + final int tex_id, final Object surface, final boolean isRecordable) { +// if (DEBUG) Log.i(TAG, "RenderHandler:setEglContext:"); + if (!(surface instanceof Surface) + && !(surface instanceof SurfaceTexture) + && !(surface instanceof SurfaceHolder)) + throw new RuntimeException("unsupported window type:" + surface); + mTexId = tex_id; + sendMessage(obtainMessage(MSG_RENDER_SET_GLCONTEXT, + isRecordable ? 1 : 0, 0, new ContextParams(sharedContext, surface))); + } + + public final void draw() { + sendMessage(obtainMessage(MSG_RENDER_DRAW, mTexId, 0, null)); + } + + public final void draw(final int tex_id) { + sendMessage(obtainMessage(MSG_RENDER_DRAW, tex_id, 0, null)); + } + + public final void draw(final float[] tex_matrix) { + sendMessage(obtainMessage(MSG_RENDER_DRAW, mTexId, 0, tex_matrix)); + } + + public final void draw(final int tex_id, final float[] tex_matrix) { + sendMessage(obtainMessage(MSG_RENDER_DRAW, tex_id, 0, tex_matrix)); + } + + public boolean isValid() { + synchronized (mThread.mSync) { + sendEmptyMessage(MSG_CHECK_VALID); + try { + mThread.mSync.wait(); + } catch (final InterruptedException e) { + } + return mThread.mSurface != null && mThread.mSurface.isValid(); + } + } + + public final void release() { +// if (DEBUG) Log.i(TAG, "release:"); + removeMessages(MSG_RENDER_SET_GLCONTEXT); + removeMessages(MSG_RENDER_DRAW); + sendEmptyMessage(MSG_RENDER_QUIT); + } + + @Override + public final void handleMessage(final Message msg) { + switch (msg.what) { + case MSG_RENDER_SET_GLCONTEXT: + final ContextParams params = (ContextParams)msg.obj; + mThread.handleSetEglContext(params.sharedContext, params.surface, msg.arg1 != 0); + break; + case MSG_RENDER_DRAW: + mThread.handleDraw(msg.arg1, (float[])msg.obj); + break; + case MSG_CHECK_VALID: + synchronized (mThread.mSync) { + mThread.mSync.notify(); + } + break; + case MSG_RENDER_QUIT: + Looper.myLooper().quit(); + break; + default: + super.handleMessage(msg); + } + } + +//******************************************************************************** +//******************************************************************************** + private RenderHandler(final RenderThread thread) { +// if (DEBUG) Log.i(TAG, "RenderHandler:"); + mThread = thread; + } + + private static final class ContextParams { + final EGLBase.IContext sharedContext; + final Object surface; + public ContextParams(final EGLBase.IContext sharedContext, final Object surface) { + this.sharedContext = sharedContext; + this.surface = surface; + } + } + + /** + * Thread to execute render methods + * You can also use HandlerThread insted of this and create Handler from its Looper. + */ + private static final class RenderThread extends Thread { + private static final String TAG_THREAD = "RenderThread"; + private final Object mSync = new Object(); + private RenderHandler mHandler; + private EGLBase mEgl; + private EGLBase.IEglSurface mTargetSurface; + private Surface mSurface; + private GLDrawer2D mDrawer; + + public RenderThread(final String name) { + super(name); + } + + public final RenderHandler getHandler() { + synchronized (mSync) { + // create rendering thread + try { + mSync.wait(); + } catch (final InterruptedException e) { + } + } + return mHandler; + } + + /** + * Set shared context and Surface + * @param shardContext + * @param surface + */ + public final void handleSetEglContext(final EGLBase.IContext shardContext, + final Object surface, final boolean isRecordable) { +// if (DEBUG) Log.i(TAG_THREAD, "setEglContext:"); + release(); + synchronized (mSync) { + mSurface = surface instanceof Surface ? (Surface)surface + : (surface instanceof SurfaceTexture + ? new Surface((SurfaceTexture)surface) : null); + } + mEgl = EGLBase.createFrom(3, shardContext, false, 0, isRecordable); + try { + mTargetSurface = mEgl.createFromSurface(surface); + mDrawer = new GLDrawer2D(isRecordable); + } catch (final Exception e) { + Log.w(TAG, e); + if (mTargetSurface != null) { + mTargetSurface.release(); + mTargetSurface = null; + } + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + } + } + + /** + * drawing + * @param tex_id + * @param tex_matrix + */ + public void handleDraw(final int tex_id, final float[] tex_matrix) { +// if (DEBUG) Log.i(TAG_THREAD, "draw"); + if (tex_id >= 0 && mTargetSurface != null) { + mTargetSurface.makeCurrent(); + mDrawer.draw(tex_id, tex_matrix, 0); + mTargetSurface.swap(); + } + } + + @Override + public final void run() { +// if (DEBUG) Log.v(TAG_THREAD, "started"); + Looper.prepare(); + synchronized (mSync) { + mHandler = new RenderHandler(this); + mSync.notify(); + } + Looper.loop(); +// if (DEBUG) Log.v(TAG_THREAD, "finishing"); + release(); + synchronized (mSync) { + mHandler = null; + } +// if (DEBUG) Log.v(TAG_THREAD, "finished"); + } + + private final void release() { +// if (DEBUG) Log.v(TAG_THREAD, "release:"); + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + synchronized (mSync) { + mSurface = null; + } + if (mTargetSurface != null) { + clear(); + mTargetSurface.release(); + mTargetSurface = null; + } + if (mEgl != null) { + mEgl.release(); + mEgl = null; + } + } + + /** + * Fill black on specific Surface + */ + private final void clear() { +// if (DEBUG) Log.v(TAG_THREAD, "clear:"); + mTargetSurface.makeCurrent(); + GLES20.glClearColor(0, 0, 0, 1); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + mTargetSurface.swap(); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHolderCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHolderCallback.java new file mode 100644 index 0000000000..44477966aa --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RenderHolderCallback.java @@ -0,0 +1,30 @@ +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 android.view.Surface; + +/** + * RenderHolderのコールバックリスナー + */ +public interface RenderHolderCallback { + public void onCreate(Surface surface); + public void onFrameAvailable(); + public void onDestroy(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererHolder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererHolder.java new file mode 100644 index 0000000000..043f5e4302 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererHolder.java @@ -0,0 +1,79 @@ +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 androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Hold shared texture that has camera frame and draw them to registered surface if needs
+ */ +public class RendererHolder extends AbstractRendererHolder { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = RendererHolder.class.getSimpleName(); + + public RendererHolder(final int width, final int height, + @Nullable final RenderHolderCallback callback) { + + this(width, height, + 3, null, EglTask.EGL_FLAG_RECORDABLE, + callback); + } + + public RendererHolder(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags, + @Nullable final RenderHolderCallback callback) { + + super(width, height, + maxClientVersion, sharedContext, flags, + callback); + } + + @NonNull + protected RendererTask createRendererTask(final int width, final int height, + final int maxClientVersion, final EGLBase.IContext sharedContext, final int flags) { + + return new MyRendererTask(this, width, height, + maxClientVersion, sharedContext, flags); + } + +//================================================================================ +// 実装 +//================================================================================ + /** + * ワーカースレッド上でOpenGL|ESを用いてマスター映像を分配描画するためのインナークラス + */ + protected static final class MyRendererTask extends RendererTask { + + public MyRendererTask(final RendererHolder parent, + final int width, final int height) { + + super(parent, width, height); + } + + public MyRendererTask(@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); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererOnFrameAvailableListener.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererOnFrameAvailableListener.java new file mode 100644 index 0000000000..0d9064f484 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererOnFrameAvailableListener.java @@ -0,0 +1,23 @@ +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. +*/ + +public interface RendererOnFrameAvailableListener { + void onFrameAvailable(long presentationTimeUs); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererSurfaceRec.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererSurfaceRec.java new file mode 100644 index 0000000000..a59125d4d9 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/RendererSurfaceRec.java @@ -0,0 +1,191 @@ +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 android.opengl.GLES20; +import android.opengl.Matrix; + +import com.serenegiant.utils.Time; + +/** + * 同じ内容のクラスだったからEffectRendererHolder/RendererHolderのインナークラスを外に出した + */ +class RendererSurfaceRec { + + /** + * ファクトリーメソッド + * @param egl + * @param surface + * @param maxFps 0以下なら最大描画フレームレート制限なし, あまり正確じゃない + * @return + */ + static RendererSurfaceRec newInstance(final EGLBase egl, + final Object surface, final int maxFps) { + + return (maxFps > 0) + ? new RendererSurfaceRecHasWait(egl, surface, maxFps) + : new RendererSurfaceRec(egl, surface); // no limitation of maxFps + } + + /** 元々の分配描画用Surface */ + private Object mSurface; + /** 分配描画用Surfaceを元に生成したOpenGL|ESで描画する為のEglSurface */ + private EGLBase.IEglSurface mTargetSurface; + final float[] mMvpMatrix = new float[16]; + protected volatile boolean mEnable = true; + + /** + * コンストラクタ, ファクトリーメソッドの使用を強制するためprivate + * @param egl + * @param surface + */ + private RendererSurfaceRec(final EGLBase egl, final Object surface) { + mSurface = surface; + mTargetSurface = egl.createFromSurface(surface); + Matrix.setIdentityM(mMvpMatrix, 0); + } + + /** + * 生成したEglSurfaceを破棄する + */ + public void release() { + if (mTargetSurface != null) { + mTargetSurface.release(); + mTargetSurface = null; + } + mSurface = null; + } + + /** + * Surfaceが有効かどうかを取得する + * @return + */ + public boolean isValid() { + return (mTargetSurface != null) && mTargetSurface.isValid(); + } + + private void check() throws IllegalStateException { + if (mTargetSurface == null) { + throw new IllegalStateException("already released"); + } + } + + /** + * Surfaceへの描画が有効かどうかを取得する + * @return + */ + public boolean isEnabled() { + return mEnable; + } + + /** + * Surfaceへの描画を一時的に有効/無効にする + * @param enable + */ + public void setEnabled(final boolean enable) { + mEnable = enable; + } + + public boolean canDraw() { + return mEnable; + } + + public void draw(final GLDrawer2D drawer, final int textId, final float[] texMatrix) { + if (mTargetSurface != null) { + mTargetSurface.makeCurrent(); + // 本来は映像が全面に描画されるので#glClearでクリアする必要はないけど + // ハングアップする機種があるのでクリアしとく + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + drawer.setMvpMatrix(mMvpMatrix, 0); + drawer.draw(textId, texMatrix, 0); + mTargetSurface.swap(); + } + } + + /** + * 指定した色で全面を塗りつぶす + * @param color + */ + public void clear(final int color) { + if (mTargetSurface != null) { + mTargetSurface.makeCurrent(); + GLES20.glClearColor( + ((color & 0x00ff0000) >>> 16) / 255.0f, // R + ((color & 0x0000ff00) >>> 8) / 255.0f, // G + ((color & 0x000000ff)) / 255.0f, // B + ((color & 0xff000000) >>> 24) / 255.0f // A + ); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + mTargetSurface.swap(); + } + } + + /** + * #drawの代わりにOpenGL|ES2を使って自前で描画する場合は + * #makeCurrentでレンダリングコンテキストを切り替えてから + * 描画後#swapを呼ぶ + */ + public void makeCurrent() throws IllegalStateException { + check(); + mTargetSurface.makeCurrent(); + } + + /** + * #drawの代わりにOpenGL|ES2を使って自前で描画する場合は + * #makeCurrentでレンダリングコンテキストを切り替えてから + * 描画後#swapを呼ぶ + */ + public void swap() throws IllegalStateException { + check(); + mTargetSurface.swap(); + } + + private static class RendererSurfaceRecHasWait extends RendererSurfaceRec { + private long mNextDraw; + private final long mIntervalsNs; + + /** + * コンストラクタ, ファクトリーメソッドの使用を強制するためprivate + * @param egl + * @param surface + * @param maxFps 正数 + */ + private RendererSurfaceRecHasWait(final EGLBase egl, + final Object surface, final int maxFps) { + + super(egl, surface); + mIntervalsNs = 1000000000L / maxFps; + mNextDraw = Time.nanoTime() + mIntervalsNs; + } + + @Override + public boolean canDraw() { + return mEnable && (Time.nanoTime() - mNextDraw > 0); + } + + @Override + public void draw(final GLDrawer2D drawer, + final int textId, final float[] texMatrix) { + + mNextDraw = Time.nanoTime() + mIntervalsNs; + super.draw(drawer, textId, texMatrix); + } + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ShaderConst.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ShaderConst.java new file mode 100644 index 0000000000..99b05961a8 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/ShaderConst.java @@ -0,0 +1,421 @@ +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 android.opengl.GLES20; + +/** + * Created by saki on 16/08/26. + * フラグメントシェーダーとかの文字列定数達を集める + */ +public class ShaderConst { + public static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65; + public static final int GL_TEXTURE_2D = 0x0DE1; + + public static final String SHADER_VERSION = "#version 100\n"; + + public static final String HEADER_2D = ""; + public static final String SAMPLER_2D = "sampler2D"; + + public static final String HEADER_OES = "#extension GL_OES_EGL_image_external : require\n"; + public static final String SAMPLER_OES = "samplerExternalOES"; + + public static final int KERNEL_SIZE3x3 = 9; + public static final int KERNEL_SIZE5x5 = 25; + + public static final int NO_TEXTURE = -1; + + public static final int[] TEX_NUMBERS = { + GLES20.GL_TEXTURE0, GLES20.GL_TEXTURE1, + GLES20.GL_TEXTURE2, GLES20.GL_TEXTURE3, + GLES20.GL_TEXTURE4, GLES20.GL_TEXTURE5, + GLES20.GL_TEXTURE6, GLES20.GL_TEXTURE7, + GLES20.GL_TEXTURE8, GLES20.GL_TEXTURE9, + GLES20.GL_TEXTURE10, GLES20.GL_TEXTURE11, + GLES20.GL_TEXTURE12, GLES20.GL_TEXTURE13, + GLES20.GL_TEXTURE14, GLES20.GL_TEXTURE15, + GLES20.GL_TEXTURE16, GLES20.GL_TEXTURE17, + GLES20.GL_TEXTURE18, GLES20.GL_TEXTURE19, + GLES20.GL_TEXTURE20, GLES20.GL_TEXTURE21, + GLES20.GL_TEXTURE22, GLES20.GL_TEXTURE23, + GLES20.GL_TEXTURE24, GLES20.GL_TEXTURE25, + GLES20.GL_TEXTURE26, GLES20.GL_TEXTURE27, + GLES20.GL_TEXTURE28, GLES20.GL_TEXTURE29, + GLES20.GL_TEXTURE30, GLES20.GL_TEXTURE31, + }; + +// 関数文字列定義 + /** + * RGBをHSVに変換 + * {R[0.0-1.0], G[0.0-1.0], B([0.0-1.0]} => {H[0.0-1.0], S[0.0-1.0], V[0.0-1.0]} + */ + public static final String FUNC_RGB2HSV + = "vec3 rgb2hsv(vec3 c) {\n" + + "vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n" + + "vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n" + + "vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n" + + "float d = q.x - min(q.w, q.y);\n" + + "float e = 1.0e-10;\n" + + "return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n" + + "}\n"; + /** + * HSVをRGBに変換 + * {H[0.0-1.0], S[0.0-1.0], V[0.0-1.0]} => {R[0.0-1.0], G[0.0-1.0], B([0.0-1.0]} + */ + public static final String FUNC_HSV2RGB + = "vec3 hsv2rgb(vec3 c) {\n" + + "vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n" + + "vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n" + + "return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n" + + "}\n"; + + /** + * RGBの輝度を取得 + * 変換係数との内積を計算するだけ + * 係数は(0.2125, 0.7154, 0.0721) + */ + public static final String FUNC_GET_INTENSITY + = "const highp vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);\n" + + "highp float getIntensity(vec3 c) {\n" + + "return dot(c.rgb, luminanceWeighting);\n" + + "}\n"; + +// 頂点シェーダー + /** + * モデルビュー変換行列とテクスチャ変換行列適用するだけの頂点シェーダー + */ + public static final String VERTEX_SHADER = SHADER_VERSION + + "uniform mat4 uMVPMatrix;\n" + // モデルビュー変換行列 + "uniform mat4 uTexMatrix;\n" + // テクスチャ変換行列 + "attribute highp vec4 aPosition;\n" + // 頂点座標 + "attribute highp vec4 aTextureCoord;\n" + // テクスチャ情報 + "varying highp vec2 vTextureCoord;\n" + // フラグメントシェーダーへ引き渡すテクスチャ座標 + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" + + "}\n"; + +// フラグメントシェーダー + public static final String FRAGMENT_SHADER_SIMPLE_OES + = SHADER_VERSION + + HEADER_OES + + "precision mediump float;\n" + + "uniform samplerExternalOES sTexture;\n" + + "varying highp vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}"; + + public static final String FRAGMENT_SHADER_SIMPLE + = SHADER_VERSION + + HEADER_2D + + "precision mediump float;\n" + + "uniform sampler2D sTexture;\n" + + "varying highp vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}"; + +// + // Simple fragment shader for use with "normal" 2D textures. + private static final String FRAGMENT_SHADER_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; + public static final String FRAGMENT_SHADER_2D + = String.format(FRAGMENT_SHADER_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT + = String.format(FRAGMENT_SHADER_BASE, HEADER_OES, SAMPLER_OES); + + // Fragment shader that converts color to black & white with a simple transformation. + private static final String FRAGMENT_SHADER_BW_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11;\n" + + " gl_FragColor = vec4(color, color, color, 1.0);\n" + + "}\n"; + public static final String FRAGMENT_SHADER_BW + = String.format(FRAGMENT_SHADER_BW_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_BW + = String.format(FRAGMENT_SHADER_BW_BASE, HEADER_OES, SAMPLER_OES); + + // Fragment shader that attempts to produce a high contrast image + private static final String FRAGMENT_SHADER_NIGHT_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = ((tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11) - 0.5 * 1.5) + 0.8;\n" + + " gl_FragColor = vec4(color, color + 0.15, color, 1.0);\n" + + "}\n"; + public static final String FRAGMENT_SHADER_NIGHT + = String.format(FRAGMENT_SHADER_NIGHT_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_NIGHT + = String.format(FRAGMENT_SHADER_NIGHT_BASE, HEADER_OES, SAMPLER_OES); + + // Fragment shader that applies a Chroma Key effect, making green pixels transparent + private static final String FRAGMENT_SHADER_CHROMA_KEY_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "void main() {\n" + + " vec4 tc = texture2D(sTexture, vTextureCoord);\n" + + " float color = ((tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11) - 0.5 * 1.5) + 0.8;\n" + + " if(tc.g > 0.6 && tc.b < 0.6 && tc.r < 0.6){ \n" + + " gl_FragColor = vec4(0, 0, 0, 0.0);\n" + + " }else{ \n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + " }\n" + + "}\n"; + public static final String FRAGMENT_SHADER_CHROMA_KEY + = String.format(FRAGMENT_SHADER_CHROMA_KEY_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_CHROMA_KEY + = String.format(FRAGMENT_SHADER_CHROMA_KEY_BASE, HEADER_OES, SAMPLER_OES); + + private static final String FRAGMENT_SHADER_SQUEEZE_BASE = SHADER_VERSION + + "%s" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = pow(r, 1.0/1.8) * 0.8;\n"+ // Squeeze it + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + public static final String FRAGMENT_SHADER_SQUEEZE + = String.format(FRAGMENT_SHADER_SQUEEZE_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_SQUEEZE + = String.format(FRAGMENT_SHADER_SQUEEZE_BASE, HEADER_OES, SAMPLER_OES); + + public static final String FRAGMENT_SHADER_EXT_TWIRL = + "#version 100\n" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " phi = phi + (1.0 - smoothstep(-0.5, 0.5, r)) * 4.0;\n"+ // Twirl it + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_TUNNEL = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " if (r > 0.5) r = 0.5;\n"+ // Tunnel + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_BULGE = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = r * smoothstep(-0.1, 0.5, r);\n"+ // Bulge + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_DENT = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = 2.0 * r - r * smoothstep(0.0, 0.7, r);\n"+ // Dent + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_FISHEYE = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " float r = length(normCoord); // to polar coords \n" + + " float phi = atan(normCoord.y + uPosition.y, normCoord.x + uPosition.x); // to polar coords \n"+ + " r = r * r / sqrt(2.0);\n"+ // Fisheye + " normCoord.x = r * cos(phi); \n" + + " normCoord.y = r * sin(phi); \n" + + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_STRETCH = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " vec2 s = sign(normCoord + uPosition);\n"+ + " normCoord = abs(normCoord);\n"+ + " normCoord = 0.5 * normCoord + 0.5 * smoothstep(0.25, 0.5, normCoord) * normCoord;\n"+ + " normCoord = s * normCoord;\n"+ + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_EXT_MIRROR = SHADER_VERSION + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "uniform vec2 uPosition;\n" + + "void main() {\n" + + " vec2 texCoord = vTextureCoord.xy;\n" + + " vec2 normCoord = 2.0 * texCoord - 1.0;\n"+ + " normCoord.x = normCoord.x * sign(normCoord.x + uPosition.x);\n"+ + " texCoord = normCoord / 2.0 + 0.5;\n"+ + " gl_FragColor = texture2D(sTexture, texCoord);\n"+ + "}\n"; + + public static final String FRAGMENT_SHADER_SOBEL_BASE = SHADER_VERSION + + "%s" + + "#define KERNEL_SIZE3x3 " + KERNEL_SIZE3x3 + "\n" + + "precision highp float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uKernel[18];\n" + + "uniform vec2 uTexOffset[KERNEL_SIZE3x3];\n" + + "uniform float uColorAdjust;\n" + + "void main() {\n" + + " vec3 t0 = texture2D(sTexture, vTextureCoord + uTexOffset[0]).rgb;\n" + + " vec3 t1 = texture2D(sTexture, vTextureCoord + uTexOffset[1]).rgb;\n" + + " vec3 t2 = texture2D(sTexture, vTextureCoord + uTexOffset[2]).rgb;\n" + + " vec3 t3 = texture2D(sTexture, vTextureCoord + uTexOffset[3]).rgb;\n" + + " vec3 t4 = texture2D(sTexture, vTextureCoord + uTexOffset[4]).rgb;\n" + + " vec3 t5 = texture2D(sTexture, vTextureCoord + uTexOffset[5]).rgb;\n" + + " vec3 t6 = texture2D(sTexture, vTextureCoord + uTexOffset[6]).rgb;\n" + + " vec3 t7 = texture2D(sTexture, vTextureCoord + uTexOffset[7]).rgb;\n" + + " vec3 t8 = texture2D(sTexture, vTextureCoord + uTexOffset[8]).rgb;\n" + + " vec3 sumH = t0 * uKernel[0] + t1 * uKernel[1] + t2 * uKernel[2]\n" + + " + t3 * uKernel[3] + t4 * uKernel[4] + t5 * uKernel[5]\n" + + " + t6 * uKernel[6] + t7 * uKernel[7] + t8 * uKernel[8];\n" + +// " vec3 sumV = t0 * uKernel[ 9] + t1 * uKernel[10] + t2 * uKernel[11]\n" + +// " + t3 * uKernel[12] + t4 * uKernel[13] + t5 * uKernel[14]\n" + +// " + t6 * uKernel[15] + t7 * uKernel[16] + t8 * uKernel[17];\n" + +// " float mag = length(abs(sumH) + abs(sumV));\n" + + " float mag = length(sumH);\n" + + " gl_FragColor = vec4(vec3(mag), 1.0);\n" + + "}\n"; + + public static final String FRAGMENT_SHADER_SOBEL + = String.format(FRAGMENT_SHADER_SOBEL_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_SOBEL + = String.format(FRAGMENT_SHADER_SOBEL_BASE, HEADER_OES, SAMPLER_OES); + + public static final float[] KERNEL_NULL = { 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f}; + public static final float[] KERNEL_SOBEL_H = { 1f, 0f, -1f, 2f, 0f, -2f, 1f, 0f, -1f, }; // ソーベル(1次微分) + public static final float[] KERNEL_SOBEL_V = { 1f, 2f, 1f, 0f, 0f, 0f, -1f, -2f, -1f, }; + public static final float[] KERNEL_SOBEL2_H = { 3f, 0f, -3f, 10f, 0f, -10f, 3f, 0f, -3f, }; + public static final float[] KERNEL_SOBEL2_V = { 3f, 10f, 3f, 0f, 0f, 0f, -3f, -10f, -3f, }; + public static final float[] KERNEL_SHARPNESS = { 0f, -1f, 0f, -1f, 5f, -1f, 0f, -1f, 0f,}; // シャープネス + public static final float[] KERNEL_EDGE_DETECT = { -1f, -1f, -1f, -1f, 8f, -1f, -1f, -1f, -1f, }; // エッジ検出 + public static final float[] KERNEL_EMBOSS = { 2f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f }; // エンボス, オフセット0.5f + public static final float[] KERNEL_SMOOTH = { 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, 1/9f, }; // 移動平均 + public static final float[] KERNEL_GAUSSIAN = { 1/16f, 2/16f, 1/16f, 2/16f, 4/16f, 2/16f, 1/16f, 2/16f, 1/16f, }; // ガウシアン(ノイズ除去/) + public static final float[] KERNEL_BRIGHTEN = { 1f, 1f, 1f, 1f, 2f, 1f, 1f, 1f, 1f, }; + public static final float[] KERNEL_LAPLACIAN = { 1f, 1f, 1f, 1f, -8f, 1f, 1f, 1f, 1f, }; // ラプラシアン(2次微分) + + private static final String FRAGMENT_SHADER_FILT3x3_BASE = SHADER_VERSION + + "%s" + + "#define KERNEL_SIZE3x3 " + KERNEL_SIZE3x3 + "\n" + + "precision highp float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform %s sTexture;\n" + + "uniform float uKernel[18];\n" + + "uniform vec2 uTexOffset[KERNEL_SIZE3x3];\n" + + "uniform float uColorAdjust;\n" + + "void main() {\n" + + " vec4 sum = vec4(0.0);\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[0]) * uKernel[0];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[1]) * uKernel[1];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[2]) * uKernel[2];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[3]) * uKernel[3];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[4]) * uKernel[4];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[5]) * uKernel[5];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[6]) * uKernel[6];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[7]) * uKernel[7];\n" + + " sum += texture2D(sTexture, vTextureCoord + uTexOffset[8]) * uKernel[8];\n" + + " gl_FragColor = sum + uColorAdjust;\n" + + "}\n"; + public static final String FRAGMENT_SHADER_FILT3x3 + = String.format(FRAGMENT_SHADER_FILT3x3_BASE, HEADER_2D, SAMPLER_2D); + public static final String FRAGMENT_SHADER_EXT_FILT3x3 + = String.format(FRAGMENT_SHADER_FILT3x3_BASE, HEADER_OES, SAMPLER_OES); + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/StaticTextureSource.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/StaticTextureSource.java new file mode 100644 index 0000000000..bb833b966e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/StaticTextureSource.java @@ -0,0 +1,553 @@ +/* + * 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. +*/ + +package com.serenegiant.glutils; + +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.opengl.GLES20; +import android.util.Log; +import android.util.SparseArray; +import android.view.Surface; +import android.view.SurfaceHolder; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * MediaCodecのデコーダーでデコードした動画や + * カメラからの映像の代わりに静止画をSurfaceへ + * 出力するためのクラス + */ +public class StaticTextureSource { + private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = StaticTextureSource.class.getSimpleName(); + + private final Object mSync = new Object(); + private RendererTask mRendererTask; + private volatile boolean isRunning; + + /** + * フレームレート指定付きコンストラクタ + * @param fps + */ + public StaticTextureSource(final float fps) { + this(null, fps); + } + + /** + * ソースの静止画を指定したコンストラクタ, フレームレートは10fps固定 + * @param bitmap + */ + public StaticTextureSource(@Nullable final Bitmap bitmap) { + this(bitmap, 10.0f); + } + + /** + * ソースの静止画とフレームレートを指定可能なコンストラクタ + * @param bitmap + * @param fps + */ + public StaticTextureSource(@Nullable final Bitmap bitmap, final float fps) { + final int width = bitmap != null ? bitmap.getWidth() : 1; + final int height = bitmap != null ? bitmap.getHeight() : 1; + mRendererTask = new RendererTask(this, width, height, fps); + new Thread(mRendererTask, TAG).start(); + if (!mRendererTask.waitReady()) { + // 初期化に失敗した時 + throw new RuntimeException("failed to start renderer thread"); + } + setBitmap(bitmap); + } + + /** + * 実行中かどうか + * @return + */ + public boolean isRunning() { + return isRunning; + } + + /** + * 関係するすべてのリソースを開放する。再利用できない + */ + public void release() { + if (DEBUG) Log.v(TAG, "release:"); + isRunning = false; + synchronized (mSync) { + mSync.notifyAll(); + } + if (mRendererTask != null) { + mRendererTask.release(); + } + synchronized (mSync) { + mRendererTask = null; + mSync.notifyAll(); + } + if (DEBUG) Log.v(TAG, "release:finished"); + } + + /** + * 分配描画用のSurfaceを追加 + * @param id 普通はSurface#hashCodeを使う + * @param surface + * @param isRecordable + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable) { + + if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface); + synchronized (mSync) { + mRendererTask.addSurface(id, surface); + } + } + + /** + * 分配描画用のSurfaceを追加 + * @param id + * @param surface + * @param isRecordable + * @param maxFps コンストラクタで指定した値より大きくしても速く描画されるわけではない + */ + public void addSurface(final int id, final Object surface, + final boolean isRecordable, final int maxFps) { + + if (DEBUG) Log.v(TAG, "addSurface:id=" + id + ",surface=" + surface); + synchronized (mSync) { + mRendererTask.addSurface(id, surface, maxFps); + } + } + + /** + * 分配描画用のSurfaceを削除 + * @param id + */ + public void removeSurface(final int id) { + if (DEBUG) Log.v(TAG, "removeSurface:id=" + id); + synchronized (mSync) { + mRendererTask.removeSurface(id); + } + } + + /** + * 強制的に現在の最新のフレームを描画要求する + * 分配描画用Surface全てが更新されるので注意 + */ + public void requestFrame() { + synchronized (mSync) { + mRendererTask.removeRequest(REQUEST_DRAW); + mRendererTask.offer(REQUEST_DRAW); + mSync.notify(); + } + } + + /** + * 追加されている分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount() { + return mRendererTask.getCount(); + } + + /** + * ソース静止画を指定 + * 既にセットされていれば古いほうが破棄される + * @param bitmap nullなら何もしない + */ + public void setBitmap(final Bitmap bitmap) { + if (DEBUG) Log.v(TAG, "setBitmap:bitmap=" + bitmap); + if (bitmap != null) { + synchronized (mSync) { + mRendererTask.setBitmap(bitmap); + } + } + } + + /** + * ソース静止画の幅を取得 + * @return 既にreleaseされていれば0 + */ + public int getWidth() { + synchronized (mSync) { + return mRendererTask != null ? mRendererTask.mVideoWidth : 0; + } + } + + /** + * ソース静止画の高さを取得 + * @return 既にreleaseされていれば0 + */ + public int getHeight() { + synchronized (mSync) { + return mRendererTask != null ? mRendererTask.mVideoHeight : 0; + } + } + + private static final int REQUEST_DRAW = 1; + private static final int REQUEST_ADD_SURFACE = 3; + private static final int REQUEST_REMOVE_SURFACE = 4; + private static final int REQUEST_SET_BITMAP = 7; + + private static class RendererTask extends EglTask { + private final Object mClientSync = new Object(); + private final SparseArray mClients + = new SparseArray(); + private final StaticTextureSource mParent; + private final long mIntervalsNs; + private GLDrawer2D mDrawer; + private int mVideoWidth, mVideoHeight; + private TextureOffscreen mImageSource; + + public RendererTask(final StaticTextureSource parent, + final int width, final int height, final float fps) { + + super(3, null, 0); + mParent = parent; + mVideoWidth = width; + mVideoHeight = height; + mIntervalsNs = fps <= 0 ? 100000000L : (long)(1000000000L / fps); + } + + /** + * ワーカースレッド開始時の処理(ここはワーカースレッド上) + */ + @Override + protected void onStart() { + if (DEBUG) Log.v(TAG, "onStart:"); + mDrawer = new GLDrawer2D(false); // GL_TEXTURE_EXTERNAL_OESを使わない + synchronized (mParent.mSync) { + mParent.isRunning = true; + mParent.mSync.notifyAll(); + } + new Thread(mParent.mOnFrameTask, TAG).start(); + 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(); + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; + } + if (mImageSource != null) { + mImageSource.release(); + mImageSource = null; + } + handleRemoveAll(); + if (DEBUG) Log.v(TAG, "onStop:finished"); + } + + @Override + protected boolean onError(final Exception e) { + if (DEBUG) Log.w(TAG, e); + return false; + } + + @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_ADD_SURFACE: + handleAddSurface(arg1, obj, arg2); + break; + case REQUEST_REMOVE_SURFACE: + handleRemoveSurface(arg1); + break; + case REQUEST_SET_BITMAP: + handleSetBitmap((Bitmap)obj); + break; + } + return null; + } + + /** + * 分配描画用のSurfaceを追加 + * @param id + * @param surface + */ + public void addSurface(final int id, final Object surface) { + addSurface(id, surface, -1); + } + + /** + * 分配描画用のSurfaceを追加 + * @param id + * @param surface + */ + public void addSurface(final int id, final Object surface, final int maxFps) { + checkFinished(); + if (!((surface instanceof SurfaceTexture) + || (surface instanceof Surface) + || (surface instanceof SurfaceHolder))) { + + throw new IllegalArgumentException( + "Surface should be one of Surface, SurfaceTexture or SurfaceHolder"); + } + synchronized (mClientSync) { + if (mClients.get(id) == null) { + for ( ; ; ) { + if (offer(REQUEST_ADD_SURFACE, id, maxFps, surface)) { + try { + mClientSync.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + try { + mClientSync.wait(10); + } catch (InterruptedException e) { + break; + } + } + } + } + } + } + + /** + * 分配描画用のSurfaceを削除 + * @param id + */ + public void removeSurface(final int id) { + synchronized (mClientSync) { + if (mClients.get(id) != null) { + for ( ; ; ) { + if (offer(REQUEST_REMOVE_SURFACE, id)) { + try { + mClientSync.wait(); + } catch (final InterruptedException e) { + // ignore + } + break; + } else { + try { + mClientSync.wait(10); + } catch (InterruptedException e) { + break; + } + } + } + } + } + } + + /** + * ソース静止画をセット + * @param bitmap + */ + public void setBitmap(@NonNull final Bitmap bitmap) { + offer(REQUEST_SET_BITMAP, bitmap); + } + + /** + * 分配描画用のSurfaceの数を取得 + * @return + */ + public int getCount() { + synchronized (mClientSync) { + return mClients.size(); + } + } + + private void checkFinished() { + if (isFinished()) { + throw new RuntimeException("already finished"); + } + } + +//================================================================================ +// ワーカースレッド上での処理 +//================================================================================ + /** + * 実際の描画処理 + */ + private void handleDraw() { +// if (DEBUG) Log.v(TAG, "handleDraw:"); + makeCurrent(); + // 各Surfaceへ描画する + if (mImageSource != null) { + final int texId = mImageSource.getTexture(); + synchronized (mClientSync) { + 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 { + client.draw(mDrawer, texId, null); // client.draw(mDrawer, mTexId, mTexMatrix); + GLHelper.checkGlError("handleSetBitmap"); + } catch (final Exception e) { + // removeSurfaceが呼ばれなかったかremoveSurfaceを呼ぶ前に破棄されてしまった + mClients.removeAt(i); + client.release(); + } + } + } + } + } else { + Log.w(TAG, "mImageSource is not ready"); + } + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glFlush(); +// if (DEBUG) Log.v(TAG, "handleDraw:finish"); + } + + /** + * 指定したIDの分配描画先Surfaceを追加する + * @param id + * @param surface + */ + private void handleAddSurface(final int id, final Object surface, final int maxFps) { + if (DEBUG) Log.v(TAG, "handleAddSurface:id=" + id); + checkSurface(); + synchronized (mClientSync) { + RendererSurfaceRec client = mClients.get(id); + if (client == null) { + try { + client = RendererSurfaceRec.newInstance(getEgl(), surface, maxFps); + 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); + } + mClientSync.notifyAll(); + } + } + + /** + * 指定したIDの分配描画先Surfaceを破棄する + * @param id + */ + private void handleRemoveSurface(final int id) { + if (DEBUG) Log.v(TAG, "handleRemoveSurface:id=" + id); + synchronized (mClientSync) { + final RendererSurfaceRec client = mClients.get(id); + if (client != null) { + mClients.remove(id); + client.release(); + } + checkSurface(); + mClientSync.notifyAll(); + } + } + + /** + * 念の為に分配描画先のSurfaceを全て破棄する + */ + private void handleRemoveAll() { + if (DEBUG) Log.v(TAG, "handleRemoveAll:"); + synchronized (mClientSync) { + final int n = mClients.size(); + RendererSurfaceRec client; + for (int i = 0; i < n; i++) { + client = mClients.valueAt(i); + if (client != null) { + makeCurrent(); + client.release(); + } + } + mClients.clear(); + } + if (DEBUG) Log.v(TAG, "handleRemoveAll:finished"); + } + + /** + * 分配描画先のSurfaceが有効かどうかをチェックして無効なものは削除する + */ + private void checkSurface() { + if (DEBUG) Log.v(TAG, "checkSurface"); + synchronized (mClientSync) { + 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"); + } + + /** + * ソース静止画をセット + * @param bitmap + */ + private void handleSetBitmap(final Bitmap bitmap) { + if (DEBUG) Log.v(TAG, "handleSetBitmap:bitmap=" + bitmap); + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + if (mImageSource == null) { + mImageSource = new TextureOffscreen(width, height, false); + GLHelper.checkGlError("handleSetBitmap"); + mImageSource.loadBitmap(bitmap); + } else { + mImageSource.loadBitmap(bitmap); + } + mVideoWidth = width; + mVideoHeight = height; + } + + } + + /** + * 一定時間おきに描画要求を送るためのRunnable + */ + private Runnable mOnFrameTask = new Runnable() { + @Override + public void run() { + final long ms = mRendererTask.mIntervalsNs / 1000000L; + final int ns = (int)(mRendererTask.mIntervalsNs % 1000000L); + for (; isRunning; ) { + if (mRendererTask == null) break; + synchronized (mSync) { + try { + mSync.wait(ms, ns); + if (mRendererTask.mImageSource != null) { + mRendererTask.removeRequest(REQUEST_DRAW); + mRendererTask.offer(REQUEST_DRAW); + mSync.notify(); + } + } catch (Exception e) { + Log.w(TAG, e); + } + } + } + } + }; + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/Texture2dProgram.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/Texture2dProgram.java new file mode 100644 index 0000000000..e66fc4ae88 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/Texture2dProgram.java @@ -0,0 +1,535 @@ +package com.serenegiant.glutils; +/* + * Copyright 2014 Google Inc. All rights reserved. + * Modified 2014-2018 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.FRAGMENT_SHADER_2D; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_BULGE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_BW; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_CHROMA_KEY; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_DENT; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_FILT3x3; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_FISHEYE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_MIRROR; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_NIGHT; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_SQUEEZE; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_STRETCH; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_TUNNEL; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_EXT_TWIRL; +import static com.serenegiant.glutils.ShaderConst.FRAGMENT_SHADER_FILT3x3; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; +import static com.serenegiant.glutils.ShaderConst.KERNEL_NULL; +import static com.serenegiant.glutils.ShaderConst.KERNEL_SIZE3x3; +import static com.serenegiant.glutils.ShaderConst.VERTEX_SHADER; + +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.util.Log; +import android.view.MotionEvent; + +import java.nio.FloatBuffer; + +/** + * GL program and supporting functions for textured 2D shapes. + */ +public class Texture2dProgram { + private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = "Texture2dProgram"; + + public enum ProgramType { + // ここはGL_TEXTURE_2D + TEXTURE_2D, +// TEXTURE_SOBEL, // フラグメントシェーダーがうまく走らなくて止まってしまう +// TEXTURE_SOBEL2, // フラグメントシェーダーがうまく走らなくて止まってしまう + TEXTURE_FILT3x3, + TEXTURE_CUSTOM, + // ここから下はGL_TEXTURE_EXTERNAL_OES + TEXTURE_EXT, + TEXTURE_EXT_BW, + TEXTURE_EXT_NIGHT, + TEXTURE_EXT_CHROMA_KEY, + TEXTURE_EXT_SQUEEZE, + TEXTURE_EXT_TWIRL, + TEXTURE_EXT_TUNNEL, + TEXTURE_EXT_BULGE, + TEXTURE_EXT_DENT, + TEXTURE_EXT_FISHEYE, + TEXTURE_EXT_STRETCH, + TEXTURE_EXT_MIRROR, +// TEXTURE_EXT_SOBEL, // フラグメントシェーダーがうまく走らなくて止まってしまう +// TEXTURE_EXT_SOBEL2, // フラグメントシェーダーがうまく走らなくて止まってしまう + TEXTURE_EXT_FILT3x3, + } + + private final Object mSync = new Object(); + private final ProgramType mProgramType; + + private float mTexWidth; + private float mTexHeight; + + // Handles to the GL program and various components of it. + private int mProgramHandle; + private final int muMVPMatrixLoc; // モデルビュー変換行列 + private final int muTexMatrixLoc; // テクスチャ行列 + private final int maPositionLoc; // + private final int maTextureCoordLoc;// + private int muKernelLoc; // カーネル行列(float配列) + private int muTexOffsetLoc; // テクスチャオフセット(カーネル行列用) + private int muColorAdjustLoc; // 色調整 + private int muTouchPositionLoc; + private int muFlagsLoc; + + private int mTextureTarget; + + protected boolean mHasKernel2; + /** Inputs for convolution filter based shaders */ + private final float[] mKernel = new float[KERNEL_SIZE3x3 * 2]; + /** Summed touch event delta */ + private final float[] mSummedTouchPosition = new float[2]; + /** Raw location of last touch event */ + private final float[] mLastTouchPosition = new float[2]; + private float[] mTexOffset; + private float mColorAdjust; + private final int[] mFlags = new int[4]; + + public Texture2dProgram(final int target, final String fss) { + this(ProgramType.TEXTURE_CUSTOM, target, VERTEX_SHADER, fss); + } + + public Texture2dProgram(final int target, final String vss, final String fss) { + this(ProgramType.TEXTURE_CUSTOM, target, vss, fss); + } + + public Texture2dProgram(final ProgramType programType) { + this(programType, 0, null, null); + } + + /** + * Prepares the program in the current EGL context. + */ + protected Texture2dProgram(final ProgramType programType, + final int target, final String vss, final String fss) { + + mProgramType = programType; + + float[] kernel = null, kernel2 = null; + switch (programType) { + case TEXTURE_2D: + mTextureTarget = GLES20.GL_TEXTURE_2D; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_2D); + break; +// case TEXTURE_SOBEL: +// mTextureTarget = GLES20.GL_TEXTURE_2D; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SOBEL); +// kernel = KERNEL_SOBEL_H; +// kernel2 = KERNEL_SOBEL_V; +// break; +// case TEXTURE_SOBEL2: +// mTextureTarget = GLES20.GL_TEXTURE_2D; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_SOBEL); +// kernel = KERNEL_SOBEL2_H; +// kernel2 = KERNEL_SOBEL2_V; +// break; + case TEXTURE_FILT3x3: + mTextureTarget = GLES20.GL_TEXTURE_2D; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_FILT3x3); + break; + case TEXTURE_EXT: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT); + break; + case TEXTURE_EXT_BW: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_BW); + break; + case TEXTURE_EXT_NIGHT: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_NIGHT); + break; + case TEXTURE_EXT_CHROMA_KEY: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_CHROMA_KEY); + break; + case TEXTURE_EXT_SQUEEZE: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_SQUEEZE); + break; + case TEXTURE_EXT_TWIRL: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_TWIRL); + break; + case TEXTURE_EXT_TUNNEL: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_TUNNEL); + break; + case TEXTURE_EXT_BULGE: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_BULGE); + break; + case TEXTURE_EXT_FISHEYE: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_FISHEYE); + break; + case TEXTURE_EXT_DENT: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_DENT); + break; + case TEXTURE_EXT_MIRROR: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_MIRROR); + break; + case TEXTURE_EXT_STRETCH: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_STRETCH); + break; +// case TEXTURE_EXT_SOBEL: +// mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_SOBEL); +// kernel = KERNEL_SOBEL_H; +// kernel2 = KERNEL_SOBEL_V; +// break; +// case TEXTURE_EXT_SOBEL2: +// mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; +// mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_SOBEL); +// kernel = KERNEL_SOBEL2_H; +// kernel2 = KERNEL_SOBEL2_V; +// break; + case TEXTURE_EXT_FILT3x3: + mTextureTarget = GL_TEXTURE_EXTERNAL_OES; + mProgramHandle = GLHelper.loadShader(VERTEX_SHADER, FRAGMENT_SHADER_EXT_FILT3x3); + break; + case TEXTURE_CUSTOM: + switch (target) { + case GLES20.GL_TEXTURE_2D: + case GLES11Ext.GL_TEXTURE_EXTERNAL_OES: + break; + default: + throw new IllegalArgumentException( + "target should be GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES"); + } + mTextureTarget = target; + mProgramHandle = GLHelper.loadShader(vss, fss); + break; + default: + throw new RuntimeException("Unhandled type " + programType); + } + if (mProgramHandle == 0) { + throw new RuntimeException("Unable to create program"); + } + if (DEBUG) Log.d(TAG, "Created program " + mProgramHandle + " (" + programType + ")"); + + // get locations of attributes and uniforms + maPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); + GLHelper.checkLocation(maPositionLoc, "aPosition"); + maTextureCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord"); + GLHelper.checkLocation(maTextureCoordLoc, "aTextureCoord"); + muMVPMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix"); + GLHelper.checkLocation(muMVPMatrixLoc, "uMVPMatrix"); + muTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix"); +// GLHelper.checkLocation(muTexMatrixLoc, "uTexMatrix"); + initLocation(kernel, kernel2); + + } + + /** + * Releases the program. + */ + public void release() { + if (DEBUG) Log.d(TAG, "deleting program " + mProgramHandle); + GLES20.glDeleteProgram(mProgramHandle); + mProgramHandle = -1; + } + + /** + * Returns the program type. + */ + public ProgramType getProgramType() { + return mProgramType; + } + + public int getProgramHandle() { + return mProgramHandle; + } + + /** + * Creates a texture object suitable for use with this program. + *

+ * On exit, the texture will be bound. + */ + public int createTextureObject() { + final int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + GLHelper.checkGlError("glGenTextures"); + + final int texId = textures[0]; + GLES20.glBindTexture(mTextureTarget, texId); + GLHelper.checkGlError("glBindTexture " + texId); + + GLES20.glTexParameterf(mTextureTarget, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf(mTextureTarget, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(mTextureTarget, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(mTextureTarget, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLHelper.checkGlError("glTexParameter"); + + return texId; + } + + /** + * Configures the effect offset + * + * This only has an effect for programs that + * use positional effects like SQUEEZE and MIRROR + */ + public void handleTouchEvent(final MotionEvent ev){ + synchronized (mSync) { + if (ev.getAction() == MotionEvent.ACTION_MOVE){ + // A finger is dragging about + if (mTexHeight != 0 && mTexWidth != 0){ + mSummedTouchPosition[0] + += (2 * (ev.getX() - mLastTouchPosition[0])) / mTexWidth; + mSummedTouchPosition[1] + += (2 * (ev.getY() - mLastTouchPosition[1])) / -mTexHeight; + mLastTouchPosition[0] = ev.getX(); + mLastTouchPosition[1] = ev.getY(); + } + } else if (ev.getAction() == MotionEvent.ACTION_DOWN){ + // The primary finger has landed + mLastTouchPosition[0] = ev.getX(); + mLastTouchPosition[1] = ev.getY(); + } + } + } + + /** + * Configures the convolution filter values. + * This only has an effect for programs that use the + * FRAGMENT_SHADER_EXT_FILT3x3 Fragment shader. + * + * @param values Normalized filter values; must be KERNEL_SIZE3x3 elements. + */ + public void setKernel(final float[] values, final float colorAdj) { + if (values.length < KERNEL_SIZE3x3) { + throw new IllegalArgumentException( + "Kernel size is " + values.length + " vs. " + KERNEL_SIZE3x3); + } + System.arraycopy(values, 0, mKernel, 0, KERNEL_SIZE3x3); + mColorAdjust = colorAdj; + } + + public void setKernel2(final float[] values) { + synchronized (mSync) { + mHasKernel2 = values != null && (values.length == KERNEL_SIZE3x3); + if (mHasKernel2) { + System.arraycopy(values, 0, mKernel, KERNEL_SIZE3x3, KERNEL_SIZE3x3); + } + } + } + + public void setColorAdjust(final float adjust) { + synchronized (mSync) { + mColorAdjust = adjust; + } + } + + /** + * Sets the size of the texture. This is used to find adjacent texels when filtering. + */ + public void setTexSize(final int width, final int height) { + mTexHeight = height; + mTexWidth = width; + final float rw = 1.0f / width; + final float rh = 1.0f / height; + + // Don't need to create a new array here, but it's syntactically convenient. + synchronized (mSync) { + mTexOffset = new float[] { + -rw, -rh, 0f, -rh, rw, -rh, + -rw, 0f, 0f, 0f, rw, 0f, + -rw, rh, 0f, rh, rw, rh + }; + } + } + + public void setFlags(final int[] flags) { + final int n = Math.min(4, flags != null ? flags.length : 0); + if (n > 0) { + synchronized (mSync) { + System.arraycopy(flags, 0, mFlags, 0, n); + } + } + } + + public void setFlag(final int index, final int value) { + if ((index >= 0) && (index < mFlags.length)) { + synchronized (mSync) { + mFlags[index] = value; + } + } + } + + /** + * Issues the draw call. Does the full setup on every call. + * + * @param mvpMatrix The 4x4 projection matrix. + * @param mvpMatrixOffset offset of mvpMatrix + * @param vertexBuffer Buffer with vertex position data. + * @param firstVertex Index of first vertex to use in vertexBuffer. + * @param vertexCount Number of vertices in vertexBuffer. + * @param coordsPerVertex The number of coordinates per vertex (e.g. x,y is 2). + * @param vertexStride Width, in bytes, of the position data for each vertex (often + * vertexCount * sizeof(float)). + * @param texMatrix A 4x4 transformation matrix for texture coords. + * @param texMatrixOffset offset of texMatrix + * @param texBuffer Buffer with vertex texture data. + * @param texStride Width, in bytes, of the texture data for each vertex. + */ + public void draw(final float[] mvpMatrix, final int mvpMatrixOffset, + final FloatBuffer vertexBuffer, final int firstVertex, + final int vertexCount, final int coordsPerVertex, final int vertexStride, + final float[] texMatrix, final int texMatrixOffset, + final FloatBuffer texBuffer, final int textureId, final int texStride) { + + GLHelper.checkGlError("draw start"); + + // シェーダープログラムを選択 + GLES20.glUseProgram(mProgramHandle); + GLHelper.checkGlError("glUseProgram"); + + // テクスチャを選択 + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(mTextureTarget, textureId); + GLHelper.checkGlError("glBindTexture"); + + synchronized (mSync) { + // モデルビュー変換行列をセット + GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mvpMatrix, mvpMatrixOffset); + GLHelper.checkGlError("glUniformMatrix4fv"); + + // テクスチャ変換行列をセット + if (muTexMatrixLoc >= 0) { + GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, texMatrix, texMatrixOffset); + GLHelper.checkGlError("glUniformMatrix4fv"); + } + + // 頂点座標バッファを有効にする("aPosition" vertex attribute) + GLES20.glEnableVertexAttribArray(maPositionLoc); + GLHelper.checkGlError("glEnableVertexAttribArray"); + GLES20.glVertexAttribPointer(maPositionLoc, coordsPerVertex, + GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); + GLHelper.checkGlError("glVertexAttribPointer"); + + // テクスチャ座標バッファを有効にする("aTextureCoord" vertex attribute) + GLES20.glEnableVertexAttribArray(maTextureCoordLoc); + GLHelper.checkGlError("glEnableVertexAttribArray"); + GLES20.glVertexAttribPointer(maTextureCoordLoc, 2, + GLES20.GL_FLOAT, false, texStride, texBuffer); + GLHelper.checkGlError("glVertexAttribPointer"); + + // カーネル関数(行列) + if (muKernelLoc >= 0) { + if (!mHasKernel2) { + GLES20.glUniform1fv(muKernelLoc, KERNEL_SIZE3x3, mKernel, 0); + } else { + GLES20.glUniform1fv(muKernelLoc, KERNEL_SIZE3x3 * 2, mKernel, 0); + } + GLHelper.checkGlError("set kernel"); + } + // テクセルオフセット + if ((muTexOffsetLoc >= 0) && (mTexOffset != null)) { + GLES20.glUniform2fv(muTexOffsetLoc, KERNEL_SIZE3x3, mTexOffset, 0); + } + // 色調整オフセット + if (muColorAdjustLoc >= 0) { + GLES20.glUniform1f(muColorAdjustLoc, mColorAdjust); + } + // タッチ座標 + if (muTouchPositionLoc >= 0){ + GLES20.glUniform2fv(muTouchPositionLoc, 1, mSummedTouchPosition, 0); + } + // フラグ + if (muFlagsLoc >= 0) { + GLES20.glUniform1iv(muFlagsLoc, 4, mFlags, 0); + } + } + + internal_draw(firstVertex, vertexCount); + + // Done -- disable vertex array, texture, and program. + GLES20.glDisableVertexAttribArray(maPositionLoc); + GLES20.glDisableVertexAttribArray(maTextureCoordLoc); + GLES20.glBindTexture(mTextureTarget, 0); + GLES20.glUseProgram(0); + } + + protected void initLocation(float[] kernel, float[] kernel2) { + muKernelLoc = GLES20.glGetUniformLocation(mProgramHandle, "uKernel"); + if (muKernelLoc < 0) { + // no kernel in this one + muKernelLoc = -1; + muTexOffsetLoc = -1; + } else { + // has kernel, must also have tex offset and color adj + muTexOffsetLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexOffset"); + if (muTexOffsetLoc < 0) { + muTexOffsetLoc = -1; + } + // 未使用だと削除されてしまうのでチェックしない +// GLHelper.checkLocation(muTexOffsetLoc, "uTexOffset"); + + // initialize default values + if (kernel == null) { + kernel = KERNEL_NULL; + } + setKernel(kernel, 0f); + setTexSize(256, 256); + } + if (kernel2 != null) { + setKernel2(kernel2); + } + + muColorAdjustLoc = GLES20.glGetUniformLocation(mProgramHandle, "uColorAdjust"); + if (muColorAdjustLoc < 0) { + muColorAdjustLoc = -1; + } + // 未使用だと削除されてしまうのでチェックしない +// GLHelper.checkLocation(muColorAdjustLoc, "uColorAdjust"); + + muTouchPositionLoc = GLES20.glGetUniformLocation(mProgramHandle, "uPosition"); + if (muTouchPositionLoc < 0) { + // Shader doesn't use position + muTouchPositionLoc = -1; + } else { + // initialize default values + //handleTouchEvent(new float[]{0f, 0f}); + } + muFlagsLoc = GLES20.glGetUniformLocation(mProgramHandle, "uFlags"); + if (muFlagsLoc < 0) { + muFlagsLoc = -1; + } else { + } + } + + protected void internal_draw(final int firstVertex, final int vertexCount) { + // Draw the rect. + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, firstVertex, vertexCount); + GLHelper.checkGlError("glDrawArrays"); + } +} \ No newline at end of file diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/TextureOffscreen.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/TextureOffscreen.java new file mode 100644 index 0000000000..95bc148e99 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/TextureOffscreen.java @@ -0,0 +1,446 @@ +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 android.graphics.Bitmap; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +/** + * テクスチャへOpenGL|ESで描画するためのオフスクリーン描画クラス + * テクスチャをカラーバッファとしてFBOに割り当てる + */ +public class TextureOffscreen { + private static final boolean DEBUG = false; + private static final String TAG = "TextureOffscreen"; + + private static final boolean DEFAULT_ADJUST_POWER2 = false; + + private final int TEX_TARGET; + private final int TEX_UNIT; + private final boolean mHasDepthBuffer, mAdjustPower2; + /** 描画領域サイズ */ + private int mWidth, mHeight; + /** テクスチャサイズ */ + private int mTexWidth, mTexHeight; + /** オフスクリーンのカラーバッファに使うテクスチャ名 */ + private int mFBOTextureName = -1; + /** // オフスクリーン用のバッファオブジェクト */ + private int mDepthBufferObj = -1, mFrameBufferObj = -1; + /** テクスチャ座標変換行列 */ + private final float[] mTexMatrix = new float[16]; + + /** + * コンストラクタ(GL_TEXTURE_2D), デプスバッファ無し + * テクスチャユニットはGL_TEXTURE0 + * @param width + * @param height + */ + public TextureOffscreen(final int width, final int height) { + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, -1, + width, height, false, DEFAULT_ADJUST_POWER2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D), デプスバッファ無し + * テクスチャユニットはGL_TEXTURE0 + * @param tex_unit + * @param width + * @param height + */ + public TextureOffscreen(final int tex_unit, + final int width, final int height) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, -1, + width, height, + false, DEFAULT_ADJUST_POWER2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D) + * テクスチャユニットはGL_TEXTURE0 + * @param width dimension of offscreen(width) + * @param height dimension of offscreen(height) + * @param use_depth_buffer set true if you use depth buffer. the depth is fixed as 16bits + */ + public TextureOffscreen(final int width, final int height, + final boolean use_depth_buffer) { + + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, -1, + width, height, use_depth_buffer, DEFAULT_ADJUST_POWER2); + } + + /** + * 既存のテクスチャ(GL_TEXTURE_2D)をwrapするためのコンストラクタ + * テクスチャユニットはGL_TEXTURE0 + * @param tex_unit + * @param width + * @param height + * @param use_depth_buffer + */ + public TextureOffscreen(final int tex_unit, + final int width, final int height, final boolean use_depth_buffer) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, -1, + width, height, + use_depth_buffer, DEFAULT_ADJUST_POWER2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D) + * テクスチャユニットはGL_TEXTURE0 + * @param width + * @param height + * @param use_depth_buffer + * @param adjust_power2 + */ + public TextureOffscreen(final int width, final int height, + final boolean use_depth_buffer, final boolean adjust_power2) { + + this(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE0, -1, + width, height, use_depth_buffer, adjust_power2); + } + + /** + * コンストラクタ(GL_TEXTURE_2D) + * @param tex_unit + * @param width + * @param height + * @param use_depth_buffer + * @param adjust_power2 + */ + public TextureOffscreen(final int tex_unit, + final int width, final int height, + final boolean use_depth_buffer, final boolean adjust_power2) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, -1, + width, height, use_depth_buffer, adjust_power2); + } + + /** + * 既存のテクスチャ(GL_TEXTURE_2D)をwrapするためのコンストラクタ, デプスバッファなし + * @param tex_id + * @param tex_unit + * @param width + * @param height + */ + public TextureOffscreen(final int tex_unit, final int tex_id, + final int width, final int height) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, tex_id, + width, height, + false, DEFAULT_ADJUST_POWER2); + } + + /** + * 既存のテクスチャ(GL_TEXTURE_2D)をwrapするためのコンストラクタ + * @param tex_unit + * @param tex_id + * @param width + * @param height + * @param use_depth_buffer + */ + public TextureOffscreen(final int tex_unit, final int tex_id, + final int width, final int height, final boolean use_depth_buffer) { + + this(GLES20.GL_TEXTURE_2D, tex_unit, tex_id, + width, height, + use_depth_buffer, DEFAULT_ADJUST_POWER2); + } + + /** + * 既存のテクスチャをwrapするためのコンストラクタ + * @param tex_target GL_TEXTURE_2D + * @param tex_id + * @param width + * @param height + * @param use_depth_buffer + * @param adjust_power2 + */ + public TextureOffscreen(final int tex_target, final int tex_unit, final int tex_id, + final int width, final int height, + final boolean use_depth_buffer, final boolean adjust_power2) { + + if (DEBUG) Log.v(TAG, "Constructor"); + TEX_TARGET = tex_target; + TEX_UNIT = tex_unit; + mWidth = width; + mHeight = height; + mHasDepthBuffer = use_depth_buffer; + mAdjustPower2 = adjust_power2; + + createFrameBuffer(width, height); + int tex = tex_id; + if (tex < 0) { + tex = genTexture(tex_target, tex_unit, mTexWidth, mTexHeight); + } + assignTexture(tex, width, height); + } + + /** 破棄する */ + public void release() { + if (DEBUG) Log.v(TAG, "release"); + releaseFrameBuffer(); + } + + /** + * オフスクリーン描画用のレンダリングバッファに切り替える + * Viewportも変更になるので必要であればunbind後にViewportの設定をすること + */ + public void bind() { +// if (DEBUG) Log.v(TAG, "bind:"); + GLES20.glActiveTexture(TEX_UNIT); + GLES20.glBindTexture(TEX_TARGET, mFBOTextureName); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObj); + GLES20.glViewport(0, 0, mWidth, mHeight); + } + + /** + * デフォルトのレンダリングバッファに戻す + */ + public void unbind() { +// if (DEBUG) Log.v(TAG, "unbind:"); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + GLES20.glActiveTexture(TEX_UNIT); + GLES20.glBindTexture(TEX_TARGET, 0); + } + + private final float[] mResultMatrix = new float[16]; + /** + * get copy of texture matrix + * @return + */ + public float[] getTexMatrix() { + System.arraycopy(mTexMatrix, 0, mResultMatrix, 0, 16); + return mResultMatrix; + } + + /** + * テクスチャ座標変換行列を取得(内部配列を直接返すので変更時は要注意) + * @return + */ + public float[] getRawTexMatrix() { + return mTexMatrix; + } + + /** + * テクスチャ変換行列のコピーを返す + * 領域チェックしていないのでoffset位置から16個以上確保しておくこと + * @param matrix + */ + public void getTexMatrix(final float[] matrix, final int offset) { + System.arraycopy(mTexMatrix, 0, matrix, offset, mTexMatrix.length); + } + + /** + * オフスクリーンテクスチャ名を取得 + * このオフスクリーンへ書き込んだ画像をテクスチャとして使って他の描画を行う場合に使用できる + * @return + */ + public int getTexture() { + return mFBOTextureName; + } + + /** 指定したテクスチャをこのオフスクリーンに割り当てる */ + public void assignTexture(final int texture_name, + final int width, final int height) { + + if ((width > mTexWidth) || (height > mTexHeight)) { + mWidth = width; + mHeight = height; + releaseFrameBuffer(); + createFrameBuffer(width, height); + } + mFBOTextureName = texture_name; + GLES20.glActiveTexture(TEX_UNIT); + // フレームバッファオブジェクトをbindする + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObj); + GLHelper.checkGlError("glBindFramebuffer " + mFrameBufferObj); + // フレームバッファにカラーバッファ(テクスチャ)を接続する + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, + TEX_TARGET, mFBOTextureName, 0); + GLHelper.checkGlError("glFramebufferTexture2D"); + + if (mHasDepthBuffer) { + // フレームバッファにデプスバッファを接続する + GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, + GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, mDepthBufferObj); + GLHelper.checkGlError("glFramebufferRenderbuffer"); + } + + // 正常に終了したかどうかを確認する + final int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException("Framebuffer not complete, status=" + status); + } + + // デフォルトのフレームバッファに戻す + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + // テクスチャ座標変換行列を初期化 + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; + } + + /** Bitmapからテクスチャを読み込む */ + public void loadBitmap(final Bitmap bitmap) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + if ((width > mTexWidth) || (height > mTexHeight)) { + mWidth = width; + mHeight = height; + releaseFrameBuffer(); + createFrameBuffer(width, height); + } + GLES20.glActiveTexture(TEX_UNIT); + GLES20.glBindTexture(TEX_TARGET, mFBOTextureName); + GLUtils.texImage2D(TEX_TARGET, 0, bitmap, 0); + GLES20.glBindTexture(TEX_TARGET, 0); + // initialize texture matrix + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; + } + + /** + * カラーバッファのためにテクスチャを生成する + * @param tex_target + * @param tex_unit + * @param tex_width + * @param tex_height + * @return + */ + private static int genTexture(final int tex_target, final int tex_unit, + final int tex_width, final int tex_height) { + // カラーバッファのためにテクスチャを生成する + final int tex_name = GLHelper.initTex(tex_target, tex_unit, + GLES20.GL_LINEAR, GLES20.GL_LINEAR, GLES20.GL_CLAMP_TO_EDGE); + // テクスチャのメモリ領域を確保する + GLES20.glTexImage2D(tex_target, 0, GLES20.GL_RGBA, tex_width, tex_height, 0, + GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); + GLHelper.checkGlError("glTexImage2D"); + return tex_name; + } + + /** オフスクリーン描画用のフレームバッファオブジェクトを生成する */ + private final void createFrameBuffer(final int width, final int height) { + final int[] ids = new int[1]; + + if (mAdjustPower2) { + // テクスチャのサイズは2の乗数にする + int w = 1; + for (; w < width; w <<= 1) ; + int h = 1; + for (; h < height; h <<= 1) ; + if (mTexWidth != w || mTexHeight != h) { + mTexWidth = w; + mTexHeight = h; + } + } else { + mTexWidth = width; + mTexHeight = height; + } + + if (mHasDepthBuffer) { + // デプスバッファが必要な場合は、レンダーバッファオブジェクトを生成・初期化する + GLES20.glGenRenderbuffers(1, ids, 0); + mDepthBufferObj = ids[0]; + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBufferObj); + // デプスバッファは16ビット + GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, + GLES20.GL_DEPTH_COMPONENT16, mTexWidth, mTexHeight); + } + // フレームバッファオブジェクトを生成してbindする + GLES20.glGenFramebuffers(1, ids, 0); + GLHelper.checkGlError("glGenFramebuffers"); + mFrameBufferObj = ids[0]; + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObj); + GLHelper.checkGlError("glBindFramebuffer " + mFrameBufferObj); + + // デフォルトのフレームバッファに戻す + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + } + + /** オフスクリーンフレームバッファを破棄 */ + private final void releaseFrameBuffer() { + final int[] names = new int[1]; + // デプスバッファがある時はデプスバッファを破棄 + if (mDepthBufferObj >= 0) { + names[0] = mDepthBufferObj; + GLES20.glDeleteRenderbuffers(1, names, 0); + mDepthBufferObj = -1; + } + // オフスクリーンのカラーバッファ用のテクスチャを破棄 + if (mFBOTextureName >= 0) { + names[0] = mFBOTextureName; + GLES20.glDeleteTextures(1, names, 0); + mFBOTextureName = -1; + } + // オフスクリーンのフレームバッファーオブジェクトを破棄 + if (mFrameBufferObj >= 0) { + names[0] = mFrameBufferObj; + GLES20.glDeleteFramebuffers(1, names, 0); + mFrameBufferObj = -1; + } + } + + /** + * get dimension(width) of this offscreen + * @return + */ + public int getWidth() { + return mWidth; + } + + /** + * get dimension(height) of this offscreen + * @return + */ + public int getHeight() { + return mHeight; + } + + /** + * get backing texture dimension(width) of this offscreen + * @return + */ + public int getTexWidth() { + return mTexWidth; + } + + /** + * get backing texture dimension(height) of this offscreen + * @return + */ + public int getTexHeight() { + return mTexHeight; + } + + public int getTexTarget() { + return TEX_TARGET; + } + + public int getTexUnit() { + return TEX_UNIT; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLDrawer2D.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLDrawer2D.java new file mode 100644 index 0000000000..54f56183df --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLDrawer2D.java @@ -0,0 +1,132 @@ +package com.serenegiant.glutils.es1; +/* + * 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_2D; +import static com.serenegiant.glutils.ShaderConst.GL_TEXTURE_EXTERNAL_OES; + +import android.opengl.GLES10; +import android.opengl.Matrix; + +import com.serenegiant.glutils.IDrawer2D; +import com.serenegiant.glutils.ITexture; +import com.serenegiant.glutils.TextureOffscreen; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public class GLDrawer2D implements IDrawer2D { + private static final float[] VERTICES = { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; + private static final float[] TEXCOORD = { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + private static final int FLOAT_SZ = Float.SIZE / 8; + private static final int VERTEX_NUM = 4; + private static final int VERTEX_SZ = VERTEX_NUM * 2; + + private final float[] mMvpMatrix = new float[16]; + private final FloatBuffer pVertex; + private final FloatBuffer pTexCoord; + private final int mTexTarget; + + /** + * コンストラクタ + * GLコンテキスト/EGLレンダリングコンテキストが有効な状態で呼ばないとダメ + * @param isOES 外部テクスチャ(GL_TEXTURE_EXTERNAL_OES)を使う場合はtrue。通常の2Dテキスチャならfalse + */ + public GLDrawer2D(final boolean isOES) { + mTexTarget = isOES ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; + pVertex = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pVertex.put(VERTICES); + pVertex.flip(); + pTexCoord = ByteBuffer.allocateDirect(VERTEX_SZ * FLOAT_SZ) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + pTexCoord.put(TEXCOORD); + pTexCoord.flip(); + // モデルビュー変換行列を初期化 + Matrix.setIdentityM(mMvpMatrix, 0); + } + + @Override + public void release() { + + } + + /** + * モデルビュー変換行列を取得(内部配列を直接返すので変更時は要注意) + * @return + */ + @Override + public float[] getMvpMatrix() { + return mMvpMatrix; + } + + /** + * モデルビュー変換行列に行列を割り当てる + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + * @return + */ + @Override + public IDrawer2D setMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(matrix, offset, mMvpMatrix, 0, 16); + return this; + } + + /** + * モデルビュー変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffsetから16個以上必須 + * @param offset + */ + @Override + public void getMvpMatrix(final float[] matrix, final int offset) { + System.arraycopy(mMvpMatrix, 0, matrix, offset, 16); + } + + @Override + public void draw(final int texId, final float[] tex_matrix, final int offset) { + // FIXME Matrixを適用 + GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); + pVertex.position(0); + GLES10.glVertexPointer(2, GLES10.GL_FLOAT, VERTEX_SZ, pVertex); +//-------------------------------------------------------------------------------- + GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); + pTexCoord.position(0); + GLES10.glTexCoordPointer(VERTEX_NUM, GLES10.GL_FLOAT, VERTEX_SZ, pTexCoord); + GLES10.glActiveTexture(GLES10.GL_TEXTURE0); + GLES10.glBindTexture(mTexTarget, texId); +//-------------------------------------------------------------------------------- + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_STRIP, 0, VERTEX_NUM); +//-------------------------------------------------------------------------------- + GLES10.glBindTexture(mTexTarget, 0); + GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); +//-------------------------------------------------------------------------------- + GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); + } + + @Override + public void draw(final ITexture texture) { + draw(texture.getTexture(), texture.getTexMatrix(), 0); + } + + @Override + public void draw(final TextureOffscreen offscreen) { + draw(offscreen.getTexture(), offscreen.getTexMatrix(), 0); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLHelper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLHelper.java new file mode 100644 index 0000000000..3a4943f935 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLHelper.java @@ -0,0 +1,459 @@ +package com.serenegiant.glutils.es1; +/* + * 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 android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.opengl.GLES10; +import android.opengl.GLES30; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +import com.serenegiant.utils.BuildCheck; + +import javax.microedition.khronos.opengles.GL10; + +/** + * OpenGL|ES用のヘルパークラス + */ +public final class GLHelper { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = "GLHelper"; + + /** + * OpenGL|ESのエラーをチェックしてlogCatに出力する + * @param op + */ + public static void checkGlError(final String op) { + final int error = GLES10.glGetError(); + if (error != GLES10.GL_NO_ERROR) { + final String msg = op + ": glError 0x" + Integer.toHexString(error); + Log.e(TAG, msg); + new Throwable(msg).printStackTrace(); +// if (DEBUG) { +// throw new RuntimeException(msg); +// } + } + } + + /** + * OpenGL|ESのエラーをチェックしてlogCatに出力する + * @param gl + * @param op + */ + public static void checkGlError(final GL10 gl, final String op) { + final int error = gl.glGetError(); + if (error != GL10.GL_NO_ERROR) { + final String msg = op + ": glError 0x" + Integer.toHexString(error); + Log.e(TAG, msg); + new Throwable(msg).printStackTrace(); +// if (DEBUG) { +// throw new RuntimeException(msg); +// } + } + } + + /** + * テクスチャ名を生成, テクスチャユニットはGL_TEXTURE0, クランプ方法はGL_CLAMP_TO_EDGE + * @param texTarget + * @param filter_param テクスチャの補完方法を指定, min/mag共に同じ値になる, GL_LINEARとかGL_NEAREST + * @return + */ + public static int initTex(final int texTarget, final int filter_param) { + return initTex(texTarget, GLES10.GL_TEXTURE0, filter_param, filter_param, GLES10.GL_CLAMP_TO_EDGE); + } + + /** + * テクスチャ名を生成 + * @param texTarget + * @param texUnit テクスチャユニット, GL_TEXTURE0...GL_TEXTURE31 + * @param min_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param mag_filter テクスチャの補間方法を指定, GL_LINEARとかGL_NEAREST + * @param wrap テクスチャのクランプ方法, GL_CLAMP_TO_EDGE + * @return + */ + public static int initTex(final int texTarget, final int texUnit, final int min_filter, final int mag_filter, final int wrap) { +// if (DEBUG) Log.v(TAG, "initTex:target=" + texTarget); + final int[] tex = new int[1]; + GLES10.glActiveTexture(texUnit); + GLES10.glGenTextures(1, tex, 0); + GLES10.glBindTexture(texTarget, tex[0]); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_WRAP_S, wrap); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_WRAP_T, wrap); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_MIN_FILTER, min_filter); + GLES10.glTexParameterx(texTarget, GLES10.GL_TEXTURE_MAG_FILTER, mag_filter); + return tex[0]; + } + + /** + * テクスチャ名を生成(GL_TEXTURE0のみ) + * @param gl + * @param texTarget + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + * @return + */ + public static int initTex(final GL10 gl, final int texTarget, final int filter_param) { +// if (DEBUG) Log.v(TAG, "initTex:target=" + texTarget); + final int[] tex = new int[1]; + gl.glActiveTexture(GL10.GL_TEXTURE0); + gl.glGenTextures(1, tex, 0); + gl.glBindTexture(texTarget, tex[0]); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_MIN_FILTER, filter_param); + gl.glTexParameterx(texTarget, GL10.GL_TEXTURE_MAG_FILTER, filter_param); + return tex[0]; + } + + /** + * delete specific texture + */ + public static void deleteTex(final int hTex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + final int[] tex = new int[] {hTex}; + GLES10.glDeleteTextures(1, tex, 0); + } + + /** + * delete specific texture + */ + public static void deleteTex(final GL10 gl, final int hTex) { +// if (DEBUG) Log.v(TAG, "deleteTex:"); + final int[] tex = new int[] {hTex}; + gl.glDeleteTextures(1, tex, 0); + } + + public static int loadTextureFromResource(final Context context, final int resId) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // get a background image from resources + // note the image format must match the bitmap format + final Drawable background = context.getResources().getDrawable(resId); + background.setBounds(0, 0, 256, 256); + background.draw(canvas); // draw the background to our bitmap + + final int[] textures = new int[1]; + + //Generate one texture pointer... + GLES10.glGenTextures(1, textures, 0); + //...and bind it to our array + GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, textures[0]); + + //Create Nearest Filtered Texture + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_NEAREST); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR); + + //Different possible texture parameters, e.g. GLES10.GL_CLAMP_TO_EDGE + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_REPEAT); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_REPEAT); + + //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); + //Clean up + bitmap.recycle(); + + return textures[0]; + } + + public static int createTextureWithTextContent (final String text) { + // Create an empty, mutable bitmap + final Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888); + // get a canvas to paint over the bitmap + final Canvas canvas = new Canvas(bitmap); + canvas.drawARGB(0,0,255,0); + + // Draw the text + final Paint textPaint = new Paint(); + textPaint.setTextSize(32); + textPaint.setAntiAlias(true); + textPaint.setARGB(0xff, 0xff, 0xff, 0xff); + // draw the text centered + canvas.drawText(text, 16, 112, textPaint); + + final int texture = initTex(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE0, GLES10.GL_NEAREST, GLES10.GL_LINEAR, GLES10.GL_REPEAT); + + // Alpha blending + // GLES10.glEnable(GLES10.GL_BLEND); + // GLES10.glBlendFunc(GLES10.GL_SRC_ALPHA, GLES10.GL_ONE_MINUS_SRC_ALPHA); + + // Use the Android GLUtils to specify a two-dimensional texture image from our bitmap + GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); + // Clean up + bitmap.recycle(); + + return texture; + } + + /** + * Checks to see if the location we obtained is valid. GLES returns -1 if a label + * could not be found, but does not set the GL error. + *

+ * Throws a RuntimeException if the location is invalid. + */ + public static void checkLocation(final int location, final String label) { + if (location < 0) { + throw new RuntimeException("Unable to locate '" + label + "' in program"); + } + } + + /** + * Writes GL version info to the log. + */ + @SuppressLint("InlinedApi") + public static void logVersionInfo() { + Log.i(TAG, "vendor : " + GLES10.glGetString(GLES10.GL_VENDOR)); + Log.i(TAG, "renderer: " + GLES10.glGetString(GLES10.GL_RENDERER)); + Log.i(TAG, "version : " + GLES10.glGetString(GLES10.GL_VERSION)); + + if (BuildCheck.isAndroid4_3()) { + final int[] values = new int[1]; + GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); + final int majorVersion = values[0]; + GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); + final int minorVersion = values[0]; + if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { + Log.i(TAG, "version: " + majorVersion + "." + minorVersion); + } + } + } + +// came from GLU + /** + * Return an error string from a GL or GLU error code. + * + * @param error - a GL or GLU error code. + * @return the error string for the input error code, or NULL if the input + * was not a valid GL or GLU error code. + */ + public static String gluErrorString(final int error) { + switch (error) { + case GLES10.GL_NO_ERROR: + return "no error"; + case GLES10.GL_INVALID_ENUM: + return "invalid enum"; + case GLES10.GL_INVALID_VALUE: + return "invalid value"; + case GLES10.GL_INVALID_OPERATION: + return "invalid operation"; + case GLES10.GL_STACK_OVERFLOW: + return "stack overflow"; + case GLES10.GL_STACK_UNDERFLOW: + return "stack underflow"; + case GLES10.GL_OUT_OF_MEMORY: + return "out of memory"; + default: + return null; + } + } + + /** + * Define a viewing transformation in terms of an eye point, a center of + * view, and an up vector. + * + * @param eyeX eye point X + * @param eyeY eye point Y + * @param eyeZ eye point Z + * @param centerX center of view X + * @param centerY center of view Y + * @param centerZ center of view Z + * @param upX up vector X + * @param upY up vector Y + * @param upZ up vector Z + */ + public static void gluLookAt(final float eyeX, final float eyeY, final float eyeZ, + final float centerX, final float centerY, final float centerZ, + final float upX, final float upY, final float upZ) { + + final float[] scratch = sScratch; + synchronized (scratch) { + Matrix.setLookAtM(scratch, 0, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, + upX, upY, upZ); + GLES10.glMultMatrixf(scratch, 0); + } + } + + /** + * Set up a 2D orthographic projection matrix + * + * @param left + * @param right + * @param bottom + * @param top + */ + public static void gluOrtho2D(final float left, final float right, + final float bottom, final float top) { + GLES10.glOrthof(left, right, bottom, top, -1.0f, 1.0f); + } + + /** + * Set up a perspective projection matrix + * + * @param fovy specifies the field of view angle, in degrees, in the Y + * direction. + * @param aspect specifies the aspect ration that determins the field of + * view in the x direction. The aspect ratio is the ratio of x + * (width) to y (height). + * @param zNear specifies the distance from the viewer to the near clipping + * plane (always positive). + * @param zFar specifies the distance from the viewer to the far clipping + * plane (always positive). + */ + public static void gluPerspective(final float fovy, final float aspect, + final float zNear, final float zFar) { + + final float top = zNear * (float) Math.tan(fovy * (Math.PI / 360.0)); + final float bottom = -top; + final float left = bottom * aspect; + final float right = top * aspect; + GLES10.glFrustumf(left, right, bottom, top, zNear, zFar); + } + + /** + * Map object coordinates into window coordinates. gluProject transforms the + * specified object coordinates into window coordinates using model, proj, + * and view. The result is stored in win. + *

+ * Note that you can use the OES_matrix_get extension, if present, to get + * the current modelView and projection matrices. + * + * @param objX object coordinates X + * @param objY object coordinates Y + * @param objZ object coordinates Z + * @param model the current modelview matrix + * @param modelOffset the offset into the model array where the modelview + * maxtrix data starts. + * @param project the current projection matrix + * @param projectOffset the offset into the project array where the project + * matrix data starts. + * @param view the current view, {x, y, width, height} + * @param viewOffset the offset into the view array where the view vector + * data starts. + * @param win the output vector {winX, winY, winZ}, that returns the + * computed window coordinates. + * @param winOffset the offset into the win array where the win vector data + * starts. + * @return A return value of GL_TRUE indicates success, a return value of + * GL_FALSE indicates failure. + */ + public static int gluProject(final float objX, final float objY, final float objZ, + final float[] model, final int modelOffset, final float[] project, final int projectOffset, + final int[] view, final int viewOffset, final float[] win, final int winOffset) { + + final float[] scratch = sScratch; + synchronized (scratch) { + final int M_OFFSET = 0; // 0..15 + final int V_OFFSET = 16; // 16..19 + final int V2_OFFSET = 20; // 20..23 + Matrix.multiplyMM(scratch, M_OFFSET, project, projectOffset, model, modelOffset); + + scratch[V_OFFSET + 0] = objX; + scratch[V_OFFSET + 1] = objY; + scratch[V_OFFSET + 2] = objZ; + scratch[V_OFFSET + 3] = 1.0f; + + Matrix.multiplyMV(scratch, V2_OFFSET, scratch, M_OFFSET, scratch, V_OFFSET); + + final float w = scratch[V2_OFFSET + 3]; + if (w == 0.0f) { + return GLES10.GL_FALSE; + } + + final float rw = 1.0f / w; + + win[winOffset] = view[viewOffset] + + view[viewOffset + 2] + * (scratch[V2_OFFSET + 0] * rw + 1.0f) + * 0.5f; + win[winOffset + 1] = + view[viewOffset + 1] + view[viewOffset + 3] + * (scratch[V2_OFFSET + 1] * rw + 1.0f) * 0.5f; + win[winOffset + 2] = (scratch[V2_OFFSET + 2] * rw + 1.0f) * 0.5f; + } + + return GL10.GL_TRUE; + } + + /** + * Map window coordinates to object coordinates. gluUnProject maps the + * specified window coordinates into object coordinates using model, proj, + * and view. The result is stored in obj. + *

+ * Note that you can use the OES_matrix_get extension, if present, to get + * the current modelView and projection matrices. + * + * @param winX window coordinates X + * @param winY window coordinates Y + * @param winZ window coordinates Z + * @param model the current modelview matrix + * @param modelOffset the offset into the model array where the modelview + * maxtrix data starts. + * @param project the current projection matrix + * @param projectOffset the offset into the project array where the project + * matrix data starts. + * @param view the current view, {x, y, width, height} + * @param viewOffset the offset into the view array where the view vector + * data starts. + * @param obj the output vector {objX, objY, objZ}, that returns the + * computed object coordinates. + * @param objOffset the offset into the obj array where the obj vector data + * starts. + * @return A return value of GL10.GL_TRUE indicates success, a return value + * of GL10.GL_FALSE indicates failure. + */ + public static int gluUnProject(final float winX, final float winY, final float winZ, + final float[] model, final int modelOffset, final float[] project, final int projectOffset, + final int[] view, final int viewOffset, final float[] obj, final int objOffset) { + + final float[] scratch = sScratch; + synchronized (scratch) { + final int PM_OFFSET = 0; // 0..15 + final int INVPM_OFFSET = 16; // 16..31 + final int V_OFFSET = 0; // 0..3 Reuses PM_OFFSET space + Matrix.multiplyMM(scratch, PM_OFFSET, project, projectOffset, model, modelOffset); + + if (!Matrix.invertM(scratch, INVPM_OFFSET, scratch, PM_OFFSET)) { + return GL10.GL_FALSE; + } + + scratch[V_OFFSET + 0] = + 2.0f * (winX - view[viewOffset + 0]) / view[viewOffset + 2] + - 1.0f; + scratch[V_OFFSET + 1] = + 2.0f * (winY - view[viewOffset + 1]) / view[viewOffset + 3] + - 1.0f; + scratch[V_OFFSET + 2] = 2.0f * winZ - 1.0f; + scratch[V_OFFSET + 3] = 1.0f; + + Matrix.multiplyMV(obj, objOffset, scratch, INVPM_OFFSET, scratch, V_OFFSET); + } + + return GL10.GL_TRUE; + } + + private static final float[] sScratch = new float[32]; +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLTexture.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLTexture.java new file mode 100644 index 0000000000..599d36a72e --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/glutils/es1/GLTexture.java @@ -0,0 +1,234 @@ +package com.serenegiant.glutils.es1; +/* + * 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 android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.opengl.GLES10; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.text.TextUtils; + +import com.serenegiant.glutils.ITexture; + +import java.io.IOException; + +/** + * OpenGL|ESのテクスチャ操作用のヘルパークラス + */ +public class GLTexture implements ITexture { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること +// private static final String TAG = "GLTexture"; + + /* package */final int mTextureTarget; + /* package */final int mTextureUnit; + /* package */int mTextureId; + /* package */final float[] mTexMatrix = new float[16]; // テクスチャ変換行列 + /* package */int mTexWidth, mTexHeight; + /* package */int mImageWidth, mImageHeight; + + /** + * コンストラクタ + * テクスチャユニットが常時GL_TEXTURE0なので複数のテクスチャを同時に使えない + * @param width + * @param height + * @param filter_param + */ + public GLTexture(final int width, final int height, final int filter_param) { + this(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE0, width, height, filter_param); + } + + /** + * コンストラクタ + * @param texTarget GL_TEXTURE_EXTERNAL_OESはだめ + * @param texUnit + * @param width テクスチャサイズ + * @param height テクスチャサイズ + * @param filter_param テクスチャの補間方法を指定 GL_LINEARとかGL_NEAREST + */ + public GLTexture(final int texTarget, final int texUnit, + final int width, final int height, final int filter_param) { + +// if (DEBUG) Log.v(TAG, String.format("コンストラクタ(%d,%d)", width, height)); + mTextureTarget = texTarget; + mTextureUnit = texUnit; + // テクスチャに使うビットマップは縦横サイズが2の乗数でないとダメ。 + // 更に、ミップマップするなら正方形でないとダメ + // 指定したwidth/heightと同じか大きい2の乗数にする + int w = 32; + for (; w < width; w <<= 1); + int h = 32; + for (; h < height; h <<= 1); + if (mTexWidth != w || mTexHeight != h) { + mTexWidth = w; + mTexHeight = h; + } +// if (DEBUG) Log.v(TAG, String.format("texSize(%d,%d)", mTexWidth, mTexHeight)); + mTextureId = GLHelper.initTex(mTextureTarget, filter_param); + // テクスチャのメモリ領域を確保する + GLES10.glTexImage2D(mTextureTarget, + 0, // ミップマップレベル0(ミップマップしない) + GLES10.GL_RGBA, // 内部フォーマット + mTexWidth, mTexHeight, // サイズ + 0, // 境界幅 + GLES10.GL_RGBA, // 引き渡すデータのフォーマット + GLES10.GL_UNSIGNED_BYTE, // データの型 + null); // ピクセルデータ無し + // テクスチャ変換行列を初期化 + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = width / (float)mTexWidth; + mTexMatrix[5] = height / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, "GLTexture:id=" + mTextureId); + } + + @Override + protected void finalize() throws Throwable { + release(); // GLコンテキスト内じゃない可能性があるのであまり良くないけど + super.finalize(); + } + + /** + * テクスチャを破棄 + * GLコンテキスト/EGLレンダリングコンテキスト内で呼び出すこと + */ + @Override + public void release() { +// if (DEBUG) Log.v(TAG, "release:"); + if (mTextureId > 0) { + GLHelper.deleteTex(mTextureId); + mTextureId = 0; + } + } + + /** + * このインスタンスで管理しているテクスチャを有効にする(バインドする) + */ + @Override + public void bind() { +// if (DEBUG) Log.v(TAG, "bind:"); + GLES10.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES10.glBindTexture(mTextureTarget, mTextureId); + } + + /** + * このインスタンスで管理しているテクスチャを無効にする(アンバインドする) + */ + @Override + public void unbind() { +// if (DEBUG) Log.v(TAG, "unbind:"); + GLES10.glActiveTexture(mTextureUnit); // テクスチャユニットを選択 + GLES10.glBindTexture(mTextureTarget, 0); + } + + /** + * テクスチャターゲットを取得(GL_TEXTURE_2D) + * @return + */ + @Override + public int getTexTarget() { return mTextureTarget; } + /** + * テクスチャ名を取得 + * @return + */ + @Override + public int getTexture() { return mTextureId; } + /** + * テクスチャ座標変換行列を取得(内部配列をそのまま返すので変更時は要注意) + * @return + */ + @Override + public float[] getTexMatrix() { return mTexMatrix; } + /** + * テクスチャ座標変換行列のコピーを取得 + * @param matrix 領域チェックしていないのでoffset位置から16個以上確保しておくこと + * @param offset + */ + @Override + public void getTexMatrix(final float[] matrix, final int offset) { + System.arraycopy(mTexMatrix, 0, matrix, offset, mTexMatrix.length); + } + /** + * テクスチャ幅を取得 + * @return + */ + @Override + public int getTexWidth() { return mTexWidth; } + /** + * テクスチャ高さを取得 + * @return + */ + @Override + public int getTexHeight() { return mTexHeight; } + + /** + * 指定したファイルから画像をテクスチャに読み込む + * ファイルが存在しないか読み込めなければIOException/NullPointerExceptionを生成 + * @param filePath + */ + @Override + public void loadTexture(final String filePath) throws NullPointerException, IOException { +// if (DEBUG) Log.v(TAG, "loadTexture:path=" + filePath); + if (TextUtils.isEmpty(filePath)) + throw new NullPointerException("image file path should not be a null"); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // Bitmapを生成せずにサイズ等の情報だけを取得する + BitmapFactory.decodeFile(filePath, options); + // テキスチャサイズ内に指定したイメージが収まるためのサブサンプリングを値を求める + final int imageWidth = options.outWidth; + final int imageHeight = options.outHeight; + int inSampleSize = 1; // サブサンプリングサイズ + if ((imageHeight > mTexHeight) || (imageWidth > mTexWidth)) { + if (imageWidth > imageHeight) { + inSampleSize = (int)Math.ceil(imageHeight / (float)mTexHeight); + } else { + inSampleSize = (int)Math.ceil(imageWidth / (float)mTexWidth); + } + } +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),tex(%d,%d),inSampleSize=%d", imageWidth, imageHeight, mTexWidth, mTexHeight, inSampleSize)); + // 実際の読み込み処理 + options.inSampleSize = inSampleSize; + options.inJustDecodeBounds = false; + loadTexture(BitmapFactory.decodeFile(filePath, options)); + } + + /** + * ビットマップからテクスチャを読み込む + * @param bitmap + * @throws NullPointerException + */ + @Override + public void loadTexture(final Bitmap bitmap) throws NullPointerException { + mImageWidth = bitmap.getWidth(); // 読み込んだイメージのサイズを取得 + mImageHeight = bitmap.getHeight(); + Bitmap texture = Bitmap.createBitmap(mTexWidth, mTexHeight, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(texture); + canvas.drawBitmap(bitmap, 0, 0, null); + bitmap.recycle(); + // テクスチャ座標変換行列を設定(読み込んだイメージサイズがテクスチャサイズにフィットするようにスケール変換) + Matrix.setIdentityM(mTexMatrix, 0); + mTexMatrix[0] = mImageWidth / (float)mTexWidth; + mTexMatrix[5] = mImageHeight / (float)mTexHeight; +// if (DEBUG) Log.v(TAG, String.format("image(%d,%d),scale(%f,%f)", mImageWidth, mImageHeight, mMvpMatrix[0], mMvpMatrix[5])); + bind(); + GLUtils.texImage2D(mTextureTarget, 0, texture, 0); + unbind(); + texture.recycle(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java index 795e394782..53269a658f 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/CameraDialog.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import android.app.Activity; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java index 2e94263c92..61b9c09d61 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/DeviceFilter.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import android.content.Context; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java index f6aee755e0..3d00d77d29 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/IFrameCallback.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import java.nio.ByteBuffer; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/ParentPreviewConstraintLayout.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/ParentPreviewConstraintLayout.java new file mode 100644 index 0000000000..96dfc06ee2 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/ParentPreviewConstraintLayout.java @@ -0,0 +1,47 @@ +package com.serenegiant.usb; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; + +/** + * @author mogoauto + */ +public abstract class ParentPreviewConstraintLayout extends ConstraintLayout { + private boolean mDestroyed; + + public ParentPreviewConstraintLayout(@NonNull Context context) { + super(context); + } + + public ParentPreviewConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public ParentPreviewConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ParentPreviewConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public boolean isDestroyed() { + return mDestroyed; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mDestroyed = false; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mDestroyed = true; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java index 963a805398..55bf0b19b7 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/Size.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import android.os.Parcel; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java index 6fee8a76bb..f58754f413 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBMonitor.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import android.annotation.SuppressLint; @@ -30,8 +7,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbInterface; @@ -52,9 +27,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -124,9 +97,12 @@ public final class USBMonitor { } public USBMonitor(final Context context, final OnDeviceConnectListener listener) { - if (DEBUG) Log.v(TAG, "USBMonitor:Constructor"); - if (listener == null) + if (DEBUG) { + Log.v(TAG, "USBMonitor:Constructor"); + } + if (listener == null) { throw new IllegalArgumentException("OnDeviceConnectListener should not null."); + } mWeakContext = new WeakReference(context); mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); mOnDeviceConnectListener = listener; @@ -225,7 +201,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public void setDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } mDeviceFilters.clear(); mDeviceFilters.add(filter); } @@ -236,7 +214,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public void addDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } mDeviceFilters.add(filter); } @@ -246,7 +226,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public void removeDeviceFilter(final DeviceFilter filter) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } mDeviceFilters.remove(filter); } @@ -256,7 +238,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public void setDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } mDeviceFilters.clear(); mDeviceFilters.addAll(filters); } @@ -267,7 +251,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public void addDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } mDeviceFilters.addAll(filters); } @@ -276,7 +262,9 @@ public final class USBMonitor { * @param filters */ public void removeDeviceFilter(final List filters) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } mDeviceFilters.removeAll(filters); } @@ -286,7 +274,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public int getDeviceCount() throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } return getDeviceList().size(); } @@ -296,7 +286,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public List getDeviceList() throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } return getDeviceList(mDeviceFilters); } @@ -307,7 +299,9 @@ public final class USBMonitor { * @throws IllegalStateException */ public List getDeviceList(final List filters) throws IllegalStateException { - if (destroyed) throw new IllegalStateException("already destroyed"); + if (destroyed) { + throw new IllegalStateException("already destroyed"); + } // get detected devices final HashMap deviceList = mUsbManager.getDeviceList(); // store those devices info before matching filter xml file diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java index d354b66ca7..f2577935a6 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/USBVendorId.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import android.util.SparseArray; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java index 15a4180b75..b73940d1b6 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/UVCCamera.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb; import android.graphics.SurfaceTexture; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java index d619c566ff..f8630fee5d 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java @@ -2,6 +2,7 @@ package com.serenegiant.usb.common; import android.annotation.TargetApi; import android.app.Activity; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.ImageFormat; @@ -21,6 +22,7 @@ import android.view.Surface; import android.view.SurfaceHolder; import com.serenegiant.usb.IFrameCallback; +import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.Size; import com.serenegiant.usb.USBMonitor; import com.serenegiant.usb.UVCCamera; @@ -66,19 +68,19 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 对外回调接口 public interface CameraCallback { - public void onOpen(); + void onOpen(); - public void onClose(); + void onClose(); - public void onStartPreview(); + void onStartPreview(); - public void onStopPreview(); + void onStopPreview(); - public void onStartRecording(); + void onStartRecording(); - public void onStopRecording(); + void onStopRecording(); - public void onError(final Exception e); + void onError(final Exception e); } public static OnEncodeResultListener mListener; @@ -178,12 +180,16 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void close() { - if (DEBUG) Log.v(TAG, "close:"); + if (DEBUG) { + Log.v(TAG, "close:"); + } if (isOpened()) { stopPreview(); sendEmptyMessage(MSG_CLOSE); } - if (DEBUG) Log.v(TAG, "close:finished"); + if (DEBUG) { + Log.v(TAG, "close:finished"); + } } // 切换分辨率 @@ -208,14 +214,18 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 关闭Camera预览 public void stopPreview() { - if (DEBUG) Log.v(TAG, "stopPreview:"); + if (DEBUG) { + Log.v(TAG, "stopPreview:"); + } removeMessages(MSG_PREVIEW_START); if (isRecording()) { stopRecording(); } if (isPreviewing()) { final CameraThread thread = mWeakThread.get(); - if (thread == null) return; + if (thread == null) { + return; + } synchronized (thread.mSync) { sendEmptyMessage(MSG_PREVIEW_STOP); if (!isCameraThread()) { @@ -229,7 +239,9 @@ public abstract class AbstractUVCCameraHandler extends Handler { } } } - if (DEBUG) Log.v(TAG, "stopPreview:finished"); + if (DEBUG) { + Log.v(TAG, "stopPreview:finished"); + } } public void captureStill(final String path, AbstractUVCCameraHandler.OnCaptureListener listener) { @@ -354,7 +366,9 @@ public abstract class AbstractUVCCameraHandler extends Handler { @Override public void handleMessage(final Message msg) { final CameraThread thread = mWeakThread.get(); - if (thread == null) return; + if (thread == null) { + return; + } switch (msg.what) { case MSG_OPEN: thread.handleOpen((USBMonitor.UsbControlBlock) msg.obj); @@ -458,13 +472,16 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public AbstractUVCCameraHandler getHandler() { - if (DEBUG) Log.v(TAG_THREAD, "getHandler:"); + if (DEBUG) { + Log.v(TAG_THREAD, "getHandler:"); + } synchronized (mSync) { - if (mHandler == null) + if (mHandler == null) { try { mSync.wait(); } catch (final InterruptedException e) { } + } } return mHandler; } @@ -510,7 +527,9 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleOpen(final USBMonitor.UsbControlBlock ctrlBlock) { - if (DEBUG) Log.v(TAG_THREAD, "handleOpen:"); + if (DEBUG) { + Log.v(TAG_THREAD, "handleOpen:"); + } handleClose(); try { final UVCCamera camera = new UVCCamera(); @@ -522,12 +541,15 @@ public abstract class AbstractUVCCameraHandler extends Handler { } catch (final Exception e) { callOnError(e); } - if (DEBUG) + if (DEBUG) { Log.i(TAG, "supportedSize:" + (mUVCCamera != null ? mUVCCamera.getSupportedSize() : null)); + } } public void handleClose() { - if (DEBUG) Log.v(TAG_THREAD, "handleClose:"); + if (DEBUG) { + Log.v(TAG_THREAD, "handleClose:"); + } handleStopPusher(); final UVCCamera camera; synchronized (mSync) { @@ -542,8 +564,12 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleStartPreview(final Object surface) { - if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); - if ((mUVCCamera == null) || mIsPreviewing) return; + if (DEBUG) { + Log.v(TAG_THREAD, "handleStartPreview:"); + } + if ((mUVCCamera == null) || mIsPreviewing) { + return; + } try { mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor); // 获取USB Camera预览数据,使用NV21颜色会失真 @@ -576,7 +602,9 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleStopPreview() { - if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:"); + if (DEBUG) { + Log.v(TAG_THREAD, "handleStopPreview:"); + } if (mIsPreviewing) { if (mUVCCamera != null) { mUVCCamera.stopPreview(); @@ -588,14 +616,20 @@ public abstract class AbstractUVCCameraHandler extends Handler { } callOnStopPreview(); } - if (DEBUG) Log.v(TAG_THREAD, "handleStopPreview:finished"); + if (DEBUG) { + Log.v(TAG_THREAD, "handleStopPreview:finished"); + } } // 捕获静态图片 public void handleCaptureStill(final String path) { - if (DEBUG) Log.v(TAG_THREAD, "handleCaptureStill:"); + if (DEBUG) { + Log.v(TAG_THREAD, "handleCaptureStill:"); + } final Activity parent = mWeakParent.get(); - if (parent == null) return; + if (parent == null) { + return; + } // mSoundPool.play(mSoundId, 0.2f, 0.2f, 0, 0, 1.0f); // play shutter sound try { final Bitmap bitmap = mWeakCameraView.get().captureStillImage(mWidth, mHeight); @@ -612,6 +646,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { os.flush(); mHandler.sendMessage(mHandler.obtainMessage(MSG_MEDIA_UPDATE, outputFile.getPath())); } catch (final IOException e) { + e.printStackTrace(); } } finally { os.close(); @@ -670,8 +705,9 @@ public abstract class AbstractUVCCameraHandler extends Handler { private H264EncodeConsumer mH264Consumer; public void handleStartPusher(RecordParams params) { - if ((mUVCCamera == null) || (mH264Consumer != null)) + if ((mUVCCamera == null) || (mH264Consumer != null)) { return; + } // // 获取USB Camera预览数据 // mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_NV21); @@ -710,8 +746,9 @@ public abstract class AbstractUVCCameraHandler extends Handler { // 停止音视频编码线程 stopAudioRecord(); stopVideoRecord(); - if(isSupportOverlay) + if(isSupportOverlay) { TxtOverlay.getInstance().release(); + } // // 停止捕获视频数据 // if (mUVCCamera != null) { // mUVCCamera.stopCapture(); @@ -877,18 +914,23 @@ public abstract class AbstractUVCCameraHandler extends Handler { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void handleUpdateMedia(final String path) { - if (DEBUG) Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path); + if (DEBUG) { + Log.v(TAG_THREAD, "handleUpdateMedia:path=" + path); + } final Activity parent = mWeakParent.get(); final boolean released = (mHandler == null) || mHandler.mReleased; if (parent != null && parent.getApplicationContext() != null) { try { - if (DEBUG) Log.i(TAG, "MediaScannerConnection#scanFile"); + if (DEBUG) { + Log.i(TAG, "MediaScannerConnection#scanFile"); + } MediaScannerConnection.scanFile(parent.getApplicationContext(), new String[]{path}, null, null); } catch (final Exception e) { Log.e(TAG, "handleUpdateMedia:", e); } - if (released || parent.isDestroyed()) + if (released || parent.isDestroyed()) { handleRelease(); + } } else { Log.w(TAG, "MainActivity already destroyed"); // give up to add this movie to MediaStore now. @@ -898,57 +940,71 @@ public abstract class AbstractUVCCameraHandler extends Handler { } public void handleRelease() { - if (DEBUG) Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); + if (DEBUG) { + Log.v(TAG_THREAD, "handleRelease:mIsRecording=" + mIsRecording); + } handleClose(); mCallbacks.clear(); if (!mIsRecording) { mHandler.mReleased = true; Looper.myLooper().quit(); } - if (DEBUG) Log.v(TAG_THREAD, "handleRelease:finished"); + if (DEBUG) { + Log.v(TAG_THREAD, "handleRelease:finished"); + } } // 自动对焦 public void handleCameraFoucs() { - if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:"); - if ((mUVCCamera == null) || !mIsPreviewing) + if (DEBUG) { + Log.v(TAG_THREAD, "handleStartPreview:"); + } + if ((mUVCCamera == null) || !mIsPreviewing) { return; + } mUVCCamera.setAutoFocus(true); } // 获取支持的分辨率 public List getSupportedSizes() { - if ((mUVCCamera == null) || !mIsPreviewing) + if ((mUVCCamera == null) || !mIsPreviewing) { return null; + } return mUVCCamera.getSupportedSizeList(); } private final MediaEncoder.MediaEncoderListener mMediaEncoderListener = new MediaEncoder.MediaEncoderListener() { @Override public void onPrepared(final MediaEncoder encoder) { - if (DEBUG) Log.v(TAG, "onPrepared:encoder=" + encoder); + if (DEBUG) { + Log.v(TAG, "onPrepared:encoder=" + encoder); + } mIsRecording = true; - if (encoder instanceof MediaVideoEncoder) + if (encoder instanceof MediaVideoEncoder) { try { mWeakCameraView.get().setVideoEncoder((MediaVideoEncoder) encoder); } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } - if (encoder instanceof MediaSurfaceEncoder) + } + if (encoder instanceof MediaSurfaceEncoder) { try { mWeakCameraView.get().setVideoEncoder((MediaSurfaceEncoder) encoder); mUVCCamera.startCapture(((MediaSurfaceEncoder) encoder).getInputSurface()); } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } + } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void onStopped(final MediaEncoder encoder) { - if (DEBUG) Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); + if (DEBUG) { + Log.v(TAG_THREAD, "onStopped:encoder=" + encoder); + } if ((encoder instanceof MediaVideoEncoder) - || (encoder instanceof MediaSurfaceEncoder)) + || (encoder instanceof MediaSurfaceEncoder)) { try { mIsRecording = false; final Activity parent = mWeakParent.get(); @@ -970,6 +1026,7 @@ public abstract class AbstractUVCCameraHandler extends Handler { } catch (final Exception e) { Log.e(TAG, "onPrepared:", e); } + } } @Override diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java index 2d01f1cd25..b102139d6d 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandler.java @@ -1,30 +1,9 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.common; import android.app.Activity; +import android.content.Context; +import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.UVCCamera; import com.serenegiant.usb.widget.CameraViewInterface; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java index d8df6c26d1..7619d39df4 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/common/UVCCameraHandlerMultiSurface.java @@ -1,32 +1,10 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.common; import android.app.Activity; import android.view.Surface; import com.serenegiant.glutils.RendererHolder; +import com.serenegiant.usb.ParentPreviewConstraintLayout; import com.serenegiant.usb.UVCCamera; import com.serenegiant.usb.widget.CameraViewInterface; @@ -123,6 +101,7 @@ public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null); } + @Override public synchronized void release() { if (mRendererHolder != null) { mRendererHolder.release(); @@ -131,7 +110,8 @@ public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler { super.release(); } - public synchronized void resize(final int width, final int height) { + @Override + public synchronized void resize(final int width, final int height) { super.resize(width, height); if (mRendererHolder != null) { mRendererHolder.resize(width, height); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java index 9a0c0bdb7e..eef9858a73 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IAudioEncoder.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.encoder; public interface IAudioEncoder { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java index 7b8429d262..24370c80ff 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/IVideoEncoder.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.encoder; public interface IVideoEncoder { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java index 3246364cff..761419aa50 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.encoder; import android.media.AudioFormat; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java index d2b99c1c1e..d11770bdc5 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java @@ -6,7 +6,8 @@ import android.os.Build; import android.os.Environment; import android.util.Log; -import com.jiangdg.usbcamera.utils.FileUtils; + +import com.mogo.usbcamera.utils.FileUtils; import java.io.IOException; import java.lang.ref.WeakReference; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java index 47eb607e08..fc3e0a38ce 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaSurfaceEncoder.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.encoder; import android.media.MediaCodec; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java index 40d26bfaa0..30d3242bb9 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/MediaVideoBufferEncoder.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.encoder; import android.media.MediaCodec; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java index 0265cac1ea..159218211a 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/RecordParams.java @@ -1,10 +1,8 @@ package com.serenegiant.usb.encoder; -/** 录制参数 - * - * Created by jiangdongguo on 2017/10/19. +/** + * 录制参数 */ - public class RecordParams { private String recordPath; private int recordDuration; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java index 3036010509..0cbf441869 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/AACEncodeConsumer.java @@ -12,23 +12,17 @@ import android.os.Build; import android.os.Process; import android.util.Log; -import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; -import java.text.SimpleDateFormat; -import java.util.Date; -/**将PCM编码为AAC - * - * Created by jianddongguo on 2017/7/21. +/** + * 将PCM编码为AAC */ - -public class AACEncodeConsumer extends Thread{ +public class AACEncodeConsumer extends Thread { private static final boolean DEBUG = false; private static final String TAG = "TMPU"; private static final String MIME_TYPE = "audio/mp4a-latm"; @@ -44,7 +38,7 @@ public class AACEncodeConsumer extends Thread{ private AudioRecord mAudioRecord; // 音频采集 private MediaCodec mAudioEncoder; // 音频编码 private OnAACEncodeResultListener listener; - private int mSamplingRateIndex = 0;//ADTS + private int mSamplingRateIndex = 0;//ADTS private boolean isEncoderStart = false; private boolean isRecMp3 = false; private boolean isExit = false; @@ -52,7 +46,7 @@ public class AACEncodeConsumer extends Thread{ private WeakReference mMuxerRef; private MediaFormat newFormat; - private static final int[] AUDIO_SOURCES = new int[] { + private static final int[] AUDIO_SOURCES = new int[]{ MediaRecorder.AudioSource.DEFAULT, MediaRecorder.AudioSource.MIC, MediaRecorder.AudioSource.CAMCORDER, @@ -60,7 +54,7 @@ public class AACEncodeConsumer extends Thread{ /** * There are 13 supported frequencies by ADTS. **/ - public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 + public static final int[] AUDIO_SAMPLING_RATES = {96000, // 0 88200, // 1 64000, // 2 48000, // 3 @@ -81,13 +75,13 @@ public class AACEncodeConsumer extends Thread{ private FileOutputStream fops; // 编码流结果回调接口 - public interface OnAACEncodeResultListener{ + public interface OnAACEncodeResultListener { void onEncodeResult(byte[] data, int offset, int length, long timestamp); } - public AACEncodeConsumer(){ - for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) { + public AACEncodeConsumer() { + for (int i = 0; i < AUDIO_SAMPLING_RATES.length; i++) { if (AUDIO_SAMPLING_RATES[i] == SAMPLE_RATE) { mSamplingRateIndex = i; break; @@ -95,16 +89,16 @@ public class AACEncodeConsumer extends Thread{ } } - public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener){ + public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener) { this.listener = listener; } - public void exit(){ + public void exit() { isExit = true; } - public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer){ - this.mMuxerRef = new WeakReference<>(mMuxer); + public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer) { + this.mMuxerRef = new WeakReference<>(mMuxer); Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null && newFormat != null) { muxer.addTrack(newFormat, false); @@ -114,7 +108,7 @@ public class AACEncodeConsumer extends Thread{ @Override public void run() { // 开启音频采集、编码 - if(! isEncoderStart){ + if (!isEncoderStart) { initAudioRecord(); initMediaCodec(); } @@ -122,16 +116,16 @@ public class AACEncodeConsumer extends Thread{ byte[] mp3Buffer = new byte[1024]; // 这里有问题,当本地录制结束后,没有写入 - while(! isExit){ + while (!isExit) { byte[] audioBuffer = new byte[2048]; // 采集音频 - int readBytes = mAudioRecord.read(audioBuffer,0,BUFFER_SIZE); + int readBytes = mAudioRecord.read(audioBuffer, 0, BUFFER_SIZE); - if(DEBUG) - Log.i(TAG,"采集音频readBytes = "+readBytes); + if (DEBUG) + Log.i(TAG, "采集音频readBytes = " + readBytes); // 编码音频 - if(readBytes > 0){ - encodeBytes(audioBuffer,readBytes); + if (readBytes > 0) { + encodeBytes(audioBuffer, readBytes); } } // 停止音频采集、编码 @@ -146,81 +140,81 @@ public class AACEncodeConsumer extends Thread{ ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers(); //返回编码器的一个输入缓存区句柄,-1表示当前没有可用的输入缓存区 int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT); - if(inputBufferIndex >= 0){ + if (inputBufferIndex >= 0) { // 绑定一个被空的、可写的输入缓存区inputBuffer到客户端 - ByteBuffer inputBuffer = null; - if(!isLollipop()){ + ByteBuffer inputBuffer = null; + if (!isLollipop()) { inputBuffer = inputBuffers[inputBufferIndex]; - }else{ + } else { inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex); } // 向输入缓存区写入有效原始数据,并提交到编码器中进行编码处理 - if(audioBuf==null || readBytes<=0){ - mAudioEncoder.queueInputBuffer(inputBufferIndex,0,0,getPTSUs(),MediaCodec.BUFFER_FLAG_END_OF_STREAM); - }else{ + if (audioBuf == null || readBytes <= 0) { + mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, 0, getPTSUs(), MediaCodec.BUFFER_FLAG_END_OF_STREAM); + } else { inputBuffer.clear(); inputBuffer.put(audioBuf); - mAudioEncoder.queueInputBuffer(inputBufferIndex,0,readBytes,getPTSUs(),0); + mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, readBytes, getPTSUs(), 0); } } // 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区 // mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间 - MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); + MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = -1; - do{ - outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo,TIMES_OUT); - if(outputBufferIndex == MediaCodec. INFO_TRY_AGAIN_LATER){ - if(DEBUG) - Log.i(TAG,"获得编码器输出缓存区超时"); - }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){ + do { + outputBufferIndex = mAudioEncoder.dequeueOutputBuffer(mBufferInfo, TIMES_OUT); + if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + if (DEBUG) + Log.i(TAG, "获得编码器输出缓存区超时"); + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { // 如果API小于21,APP需要重新绑定编码器的输入缓存区; // 如果API大于21,则无需处理INFO_OUTPUT_BUFFERS_CHANGED - if(!isLollipop()){ + if (!isLollipop()) { outputBuffers = mAudioEncoder.getOutputBuffers(); } - }else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 编码器输出缓存区格式改变,通常在存储数据之前且只会改变一次 // 这里设置混合器视频轨道,如果音频已经添加则启动混合器(保证音视频同步) - if(DEBUG) - Log.i(TAG,"编码器输出缓存区格式改变,添加视频轨道到混合器"); + if (DEBUG) + Log.i(TAG, "编码器输出缓存区格式改变,添加视频轨道到混合器"); synchronized (AACEncodeConsumer.this) { newFormat = mAudioEncoder.getOutputFormat(); - if(mMuxerRef != null){ + if (mMuxerRef != null) { Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null) { muxer.addTrack(newFormat, false); } } } - }else{ + } else { // 当flag属性置为BUFFER_FLAG_CODEC_CONFIG后,说明输出缓存区的数据已经被消费了 - if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0){ - if(DEBUG) - Log.i(TAG,"编码数据被消费,BufferInfo的size属性置0"); + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + if (DEBUG) + Log.i(TAG, "编码数据被消费,BufferInfo的size属性置0"); mBufferInfo.size = 0; } // 数据流结束标志,结束本次循环 - if((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){ - if(DEBUG) - Log.i(TAG,"数据流结束,退出循环"); + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + if (DEBUG) + Log.i(TAG, "数据流结束,退出循环"); break; } // 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据 ByteBuffer mBuffer = ByteBuffer.allocate(10240); ByteBuffer outputBuffer = null; - if(!isLollipop()){ - outputBuffer = outputBuffers[outputBufferIndex]; - }else{ - outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); + if (!isLollipop()) { + outputBuffer = outputBuffers[outputBufferIndex]; + } else { + outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex); } - if(mBufferInfo.size != 0){ + if (mBufferInfo.size != 0) { // 获取输出缓存区失败,抛出异常 - if(outputBuffer == null){ - throw new RuntimeException("encodecOutputBuffer"+outputBufferIndex+"was null"); + if (outputBuffer == null) { + throw new RuntimeException("encodecOutputBuffer" + outputBufferIndex + "was null"); } // 添加视频流到混合器 - if(mMuxerRef != null){ + if (mMuxerRef != null) { Mp4MediaMuxer muxer = mMuxerRef.get(); if (muxer != null) { muxer.pumpStream(outputBuffer, mBufferInfo, false); @@ -234,25 +228,25 @@ public class AACEncodeConsumer extends Thread{ addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7); mBuffer.flip(); // 将AAC回调给MainModelImpl进行push - if(listener != null){ - Log.i(TAG,"----->得到aac数据流<-----"); - listener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); + if (listener != null) { + Log.i(TAG, "----->得到aac数据流<-----"); + listener.onEncodeResult(mBuffer.array(), 0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000); } } // 处理结束,释放输出缓存区资源 - mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false); + mAudioEncoder.releaseOutputBuffer(outputBufferIndex, false); } - }while (outputBufferIndex >= 0); + } while (outputBufferIndex >= 0); } - private void initAudioRecord(){ - if(DEBUG) - Log.d(TAG,"AACEncodeConsumer-->开始采集音频"); + private void initAudioRecord() { + if (DEBUG) + Log.d(TAG, "AACEncodeConsumer-->开始采集音频"); // 设置进程优先级 Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); - for (final int src: AUDIO_SOURCES) { + for (final int src : AUDIO_SOURCES) { try { mAudioRecord = new AudioRecord(src, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize); @@ -272,18 +266,18 @@ public class AACEncodeConsumer extends Thread{ mAudioRecord.startRecording(); } - private void initMediaCodec(){ - if(DEBUG) - Log.d(TAG,"AACEncodeConsumer-->开始编码音频"); + private void initMediaCodec() { + if (DEBUG) + Log.d(TAG, "AACEncodeConsumer-->开始编码音频"); MediaCodecInfo mCodecInfo = selectSupportCodec(MIME_TYPE); - if(mCodecInfo == null){ - Log.e(TAG,"编码器不支持"+MIME_TYPE+"类型"); + if (mCodecInfo == null) { + Log.e(TAG, "编码器不支持" + MIME_TYPE + "类型"); return; } - try{ + try { mAudioEncoder = MediaCodec.createByCodecName(mCodecInfo.getName()); - }catch(IOException e){ - Log.e(TAG,"创建编码器失败"+e.getMessage()); + } catch (IOException e) { + Log.e(TAG, "创建编码器失败" + e.getMessage()); e.printStackTrace(); } MediaFormat format = new MediaFormat(); @@ -299,9 +293,9 @@ public class AACEncodeConsumer extends Thread{ } private void stopAudioRecord() { - if(DEBUG) - Log.d(TAG,"AACEncodeConsumer-->停止采集音频"); - if(mAudioRecord != null){ + if (DEBUG) + Log.d(TAG, "AACEncodeConsumer-->停止采集音频"); + if (mAudioRecord != null) { mAudioRecord.stop(); mAudioRecord.release(); mAudioRecord = null; @@ -309,9 +303,9 @@ public class AACEncodeConsumer extends Thread{ } private void stopMediaCodec() { - if(DEBUG) - Log.d(TAG,"AACEncodeConsumer-->停止编码音频"); - if(mAudioEncoder != null){ + if (DEBUG) + Log.d(TAG, "AACEncodeConsumer-->停止编码音频"); + if (mAudioEncoder != null) { mAudioEncoder.stop(); mAudioEncoder.release(); mAudioEncoder = null; @@ -320,28 +314,28 @@ public class AACEncodeConsumer extends Thread{ } // API>=21 - private boolean isLollipop(){ + private boolean isLollipop() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } // API<=19 - private boolean isKITKAT(){ + private boolean isKITKAT() { return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT; } - private long getPTSUs(){ - long result = System.nanoTime()/1000; - if(result < prevPresentationTimes){ - result = (prevPresentationTimes - result ) + result; + private long getPTSUs() { + long result = System.nanoTime() / 1000; + if (result < prevPresentationTimes) { + result = (prevPresentationTimes - result) + result; } return result; } /** * 遍历所有编解码器,返回第一个与指定MIME类型匹配的编码器 - * 判断是否有支持指定mime类型的编码器 - * */ - private MediaCodecInfo selectSupportCodec(String mimeType){ + * 判断是否有支持指定mime类型的编码器 + */ + private MediaCodecInfo selectSupportCodec(String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); @@ -371,7 +365,7 @@ public class AACEncodeConsumer extends Thread{ } - private short[] transferByte2Short(byte[] data,int readBytes){ + private short[] transferByte2Short(byte[] data, int readBytes) { // byte[] 转 short[],数组长度缩减一半 int shortLen = readBytes / 2; // 将byte[]数组装如ByteBuffer缓冲区 diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java index 9310ce1ae4..8ccd36026a 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/H264EncodeConsumer.java @@ -17,9 +17,7 @@ import java.nio.ByteBuffer; /** * 对YUV视频流进行编码 - * Created by jiangdongguo on 2017/5/6. */ - @SuppressWarnings("deprecation") public class H264EncodeConsumer extends Thread { private static final boolean DEBUG = false; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java index 64307af008..1b2fc40c5b 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/encoder/biz/Mp4MediaMuxer.java @@ -10,11 +10,9 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; -/**Mp4封装混合器 - * - * Created by jianddongguo on 2017/7/28. +/** + * Mp4封装混合器 */ - public class Mp4MediaMuxer { private static final boolean VERBOSE = false; private static final String TAG = Mp4MediaMuxer.class.getSimpleName(); diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java index 076758db30..c616a494ef 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/AspectRatioTextureView.java @@ -27,37 +27,38 @@ import android.content.Context; import android.util.AttributeSet; import android.view.TextureView; -import com.serenegiant.widget.IAspectRatioView; - /** * change the view size with keeping the specified aspect ratio. * if you set this view with in a FrameLayout and set property "android:layout_gravity="center", * you can show this view in the center of screen and keep the aspect ratio of content * XXX it is better that can set the aspect ratio as xml property + * + * @author mogoauto */ -public class AspectRatioTextureView extends TextureView // API >= 14 - implements IAspectRatioView { +public class AspectRatioTextureView extends TextureView // API >= 14 + implements IAspectRatioView { - private static final boolean DEBUG = true; // TODO set false on release - private static final String TAG = "AbstractCameraView"; + /**TODO set false on release*/ + private static final boolean DEBUG = true; + private static final String TAG = "AbstractCameraView"; private double mRequestedAspect = -1.0; - private CameraViewInterface.Callback mCallback; + private CameraViewInterface.Callback mCallback; - public AspectRatioTextureView(final Context context) { - this(context, null, 0); - } + public AspectRatioTextureView(final Context context) { + this(context, null, 0); + } - public AspectRatioTextureView(final Context context, final AttributeSet attrs) { - this(context, attrs, 0); - } + public AspectRatioTextureView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } - public AspectRatioTextureView(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - } + public AspectRatioTextureView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + } - // 设置屏幕宽高比 - @Override + // 设置屏幕宽高比 + @Override public void setAspectRatio(final double aspectRatio) { if (aspectRatio < 0) { throw new IllegalArgumentException(); @@ -68,45 +69,45 @@ public class AspectRatioTextureView extends TextureView // API >= 14 } } - @Override + @Override public void setAspectRatio(final int width, final int height) { - setAspectRatio(width / (double)height); + setAspectRatio(width / (double) height); } - @Override - public double getAspectRatio() { - return mRequestedAspect; - } + @Override + public double getAspectRatio() { + return mRequestedAspect; + } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mRequestedAspect > 0) { - int initialWidth = MeasureSpec.getSize(widthMeasureSpec); - int initialHeight = MeasureSpec.getSize(heightMeasureSpec); + if (mRequestedAspect > 0) { + int initialWidth = MeasureSpec.getSize(widthMeasureSpec); + int initialHeight = MeasureSpec.getSize(heightMeasureSpec); - final int horizPadding = getPaddingLeft() + getPaddingRight(); - final int vertPadding = getPaddingTop() + getPaddingBottom(); - initialWidth -= horizPadding; - initialHeight -= vertPadding; + final int horizPadding = getPaddingLeft() + getPaddingRight(); + final int vertPadding = getPaddingTop() + getPaddingBottom(); + initialWidth -= horizPadding; + initialHeight -= vertPadding; - final double viewAspectRatio = (double)initialWidth / initialHeight; - final double aspectDiff = mRequestedAspect / viewAspectRatio - 1; + final double viewAspectRatio = (double) initialWidth / initialHeight; + final double aspectDiff = mRequestedAspect / viewAspectRatio - 1; - if (Math.abs(aspectDiff) > 0.01) { - if (aspectDiff > 0) { - // width priority decision - initialHeight = (int) (initialWidth / mRequestedAspect); - } else { - // height priority decision - initialWidth = (int) (initialHeight * mRequestedAspect); - } - initialWidth += horizPadding; - initialHeight += vertPadding; - widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY); - heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY); - } - } + if (Math.abs(aspectDiff) > 0.01) { + if (aspectDiff > 0) { + // width priority decision + initialHeight = (int) (initialWidth / mRequestedAspect); + } else { + // height priority decision + initialWidth = (int) (initialHeight * mRequestedAspect); + } + initialWidth += horizPadding; + initialHeight += vertPadding; + widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY); + heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY); + } + } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java index af903d6e92..413174669c 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/CameraViewInterface.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.widget; import android.graphics.Bitmap; @@ -28,7 +5,6 @@ import android.graphics.SurfaceTexture; import android.view.Surface; import com.serenegiant.usb.encoder.IVideoEncoder; -import com.serenegiant.widget.IAspectRatioView; public interface CameraViewInterface extends IAspectRatioView { public interface Callback { diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/IAspectRatioView.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/IAspectRatioView.java new file mode 100644 index 0000000000..7e6ca85240 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/IAspectRatioView.java @@ -0,0 +1,9 @@ +package com.serenegiant.usb.widget; + +public interface IAspectRatioView { + void setAspectRatio(double var1); + + void setAspectRatio(int var1, int var2); + + double getAspectRatio(); +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java index b1880b96d1..539a67318e 100644 --- a/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/usb/widget/UVCCameraTextureView.java @@ -1,26 +1,3 @@ -/* - * UVCCamera - * library and sample to access to UVC web camera on non-rooted Android device - * - * Copyright (c) 2014-2017 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. - * - * All files in the folder are under this Apache License, Version 2.0. - * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder - * may have a different license, see the respective files. - */ - package com.serenegiant.usb.widget; import android.content.Context; diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/AssetsHelper.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/AssetsHelper.java new file mode 100644 index 0000000000..6b17620d0c --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/AssetsHelper.java @@ -0,0 +1,24 @@ +package com.serenegiant.utils; + +import android.content.res.AssetManager; + +import androidx.annotation.NonNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class AssetsHelper { + + public static String loadString(@NonNull final AssetManager assets, @NonNull final String name) throws IOException { + final StringBuffer sb = new StringBuffer(); + final char[] buf = new char[1024]; + final BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(name))); + int r = reader.read(buf); + while (r > 0) { + sb.append(buf, 0, r); + r = reader.read(buf); + } + return sb.toString(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/BuildCheck.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/BuildCheck.java new file mode 100644 index 0000000000..fd789a5e1b --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/BuildCheck.java @@ -0,0 +1,458 @@ +package com.serenegiant.utils; + +import android.os.Build; + +public final class BuildCheck { + + private static final boolean check(final int value) { + return (Build.VERSION.SDK_INT >= value); + } + + /** + * Magic version number for a current development build, + * which has not yet turned into an official release. API=10000 + * @return + */ + public static boolean isCurrentDevelopment() { + return (Build.VERSION.SDK_INT == Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + /** + * October 2008: The original, first, version of Android. Yay!, API>=1 + * @return + */ + public static boolean isBase() { + return check(Build.VERSION_CODES.BASE); + } + + /** + * February 2009: First Android update, officially called 1.1., API>=2 + * @return + */ + public static boolean isBase11() { + return check(Build.VERSION_CODES.BASE_1_1); + } + + /** + * May 2009: Android 1.5., API>=3 + * @return + */ + public static boolean isCupcake() { + return check(Build.VERSION_CODES.CUPCAKE); + } + + /** + * May 2009: Android 1.5., API>=3 + * @return + */ + public static boolean isAndroid1_5() { + return check(Build.VERSION_CODES.CUPCAKE); + } + + /** + * September 2009: Android 1.6., API>=4 + * @return + */ + public static boolean isDonut() { + return check(Build.VERSION_CODES.DONUT); + } + + /** + * September 2009: Android 1.6., API>=4 + * @return + */ + public static boolean isAndroid1_6() { + return check(Build.VERSION_CODES.DONUT); + } + + /** + * November 2009: Android 2.0, API>=5 + * @return + */ + public static boolean isEclair() { + return check(Build.VERSION_CODES.ECLAIR); + } + + /** + * November 2009: Android 2.0, API>=5 + * @return + */ + public static boolean isAndroid2_0() { + return check(Build.VERSION_CODES.ECLAIR); + } + + /** + * December 2009: Android 2.0.1, API>=6 + * @return + */ + public static boolean isEclair01() { + return check(Build.VERSION_CODES.ECLAIR_0_1); + } + + /** + * January 2010: Android 2.1, API>=7 + * @return + */ + public static boolean isEclairMR1() { + return check(Build.VERSION_CODES.ECLAIR_MR1); + } + + /** + * June 2010: Android 2.2, API>=8 + * @return + */ + public static boolean isFroyo() { + return check(Build.VERSION_CODES.FROYO); + } + + /** + * June 2010: Android 2.2, API>=8 + * @return + */ + public static boolean isAndroid2_2() { + return check(Build.VERSION_CODES.FROYO); + } + + /** + * November 2010: Android 2.3, API>=9 + * @return + */ + public static boolean isGingerBread() { + return check(Build.VERSION_CODES.GINGERBREAD); + } + + /** + * November 2010: Android 2.3, API>=9 + * @return + */ + public static boolean isAndroid2_3() { + return check(Build.VERSION_CODES.GINGERBREAD); + } + + /** + * February 2011: Android 2.3.3., API>=10 + * @return + */ + public static boolean isGingerBreadMR1() { + return check(Build.VERSION_CODES.GINGERBREAD_MR1); + } + + /** + * February 2011: Android 2.3.3., API>=10 + * @return + */ + public static boolean isAndroid2_3_3() { + return check(Build.VERSION_CODES.GINGERBREAD_MR1); + } + + /** + * February 2011: Android 3.0., API>=11 + * @return + */ + public static boolean isHoneyComb() { + return check(Build.VERSION_CODES.HONEYCOMB); + } + + /** + * February 2011: Android 3.0., API>=11 + * @return + */ + public static boolean isAndroid3() { + return check(Build.VERSION_CODES.HONEYCOMB); + } + + /** + * May 2011: Android 3.1., API>=12 + * @return + */ + public static boolean isHoneyCombMR1() { + return check(Build.VERSION_CODES.HONEYCOMB_MR1); + } + + /** + * May 2011: Android 3.1., API>=12 + * @return + */ + public static boolean isAndroid3_1() { + return check(Build.VERSION_CODES.HONEYCOMB_MR1); + } + + /** + * June 2011: Android 3.2., API>=13 + * @return + */ + public static boolean isHoneyCombMR2() { + return check(Build.VERSION_CODES.HONEYCOMB_MR2); + } + + /** + * June 2011: Android 3.2., API>=13 + * @return + */ + public static boolean isAndroid3_2() { + return check(Build.VERSION_CODES.HONEYCOMB_MR2); + } + + /** + * October 2011: Android 4.0., API>=14 + * @return + */ + public static boolean isIcecreamSandwich() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH); + } + + /** + * October 2011: Android 4.0., API>=14 + * @return + */ + public static boolean isAndroid4() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH); + } + + /** + * December 2011: Android 4.0.3., API>=15 + * @return + */ + public static boolean isIcecreamSandwichMR1() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + } + + /** + * December 2011: Android 4.0.3., API>=15 + * @return + */ + public static boolean isAndroid4_0_3() { + return check(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1); + } + + /** + * June 2012: Android 4.1., API>=16 + * @return + */ + public static boolean isJellyBean() { + return check(Build.VERSION_CODES.JELLY_BEAN); + } + + /** + * June 2012: Android 4.1., API>=16 + * @return + */ + public static boolean isAndroid4_1() { + return check(Build.VERSION_CODES.JELLY_BEAN); + } + + /** + * November 2012: Android 4.2, Moar jelly beans!, API>=17 + * @return + */ + public static boolean isJellyBeanMr1() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR1); + } + + /** + * November 2012: Android 4.2, Moar jelly beans!, API>=17 + * @return + */ + public static boolean isAndroid4_2() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR1); + } + + /** + * July 2013: Android 4.3, the revenge of the beans., API>=18 + * @return + */ + public static boolean isJellyBeanMR2() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + /** + * July 2013: Android 4.3, the revenge of the beans., API>=18 + * @return + */ + public static boolean isAndroid4_3() { + return check(Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + /** + * October 2013: Android 4.4, KitKat, another tasty treat., API>=19 + * @return + */ + public static boolean isKitKat() { + return check(Build.VERSION_CODES.KITKAT); + } + + /** + * October 2013: Android 4.4, KitKat, another tasty treat., API>=19 + * @return + */ + public static boolean isAndroid4_4() { + return check(Build.VERSION_CODES.KITKAT); + } + + /** + * Android 4.4W: KitKat for watches, snacks on the run., API>=20 + * @return + */ + public static boolean isKitKatWatch() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH); + } + + /** + * Lollipop. A flat one with beautiful shadows. But still tasty., API>=21 + * @return + */ + public static boolean isL() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); + } + + /** + * Lollipop. A flat one with beautiful shadows. But still tasty., API>=21 + * @return + */ + public static boolean isLollipop() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); + } + + /** + * Lollipop. A flat one with beautiful shadows. But still tasty., API>=21 + * @return + */ + public static boolean isAndroid5() { + return check(Build.VERSION_CODES.LOLLIPOP); + } + + /** + * Lollipop with an extra sugar coating on the outside!, API>=22 + * @return + */ + public static boolean isLollipopMR1() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1); + } + + /** + * Marshmallow. A flat one with beautiful shadows. But still tasty., API>=23 + * @return + */ + public static boolean isM() { + return check(Build.VERSION_CODES.M); + } + + /** + * Marshmallow. A flat one with beautiful shadows. But still tasty., API>=23 + * @return + */ + public static boolean isMarshmallow() { + return check(Build.VERSION_CODES.M); + } + + /** + * Marshmallow. A flat one with beautiful shadows. But still tasty., API>=23 + * @return + */ + public static boolean isAndroid6() { + return check(Build.VERSION_CODES.M); + } + + /** + * 虫歯の元, API >= 24 + * @return + */ + public static boolean isN() { + return check(Build.VERSION_CODES.N); + } + + /** + * 歯にくっつくやつ, API >= 24 + * @return + */ + public static boolean isNougat() { + return check(Build.VERSION_CODES.N); + } + /** + * API >= 24 + * @return + */ + public static boolean isAndroid7() { + return check(Build.VERSION_CODES.N); + } + + /** + * API>=25 + * @return + */ + public static boolean isNMR1() { + return check(Build.VERSION_CODES.N_MR1); + } + + /** + * API>=25 + * @return + */ + public static boolean isNougatMR1() { + return check(Build.VERSION_CODES.N_MR1); + } + + /** + * おれおれぇー API>=26 + * @return + */ + public static boolean isO() { + return check(Build.VERSION_CODES.O); + } + + /** + * おれおれぇー API>=26 + * @return + */ + public static boolean isOreo() { + return check(Build.VERSION_CODES.O); + } + + /** + * おれおれぇー API>=26 + * @return + */ + public static boolean isAndroid8() { + return check(Build.VERSION_CODES.O); + } + + /** + * おれおれぇー API>=27 + * @return + */ + public static boolean isOMR1() { + return check(Build.VERSION_CODES.O_MR1); + } + + /** + * おれおれぇー MR1 API>=27 + * @return + */ + public static boolean isOreoMR1() { + return check((Build.VERSION_CODES.O_MR1)); + } + + /** + * おっ!ぱい API>=28 + * @return + */ + public static boolean isP() { + return check((Build.VERSION_CODES.P)); + } + + /** + * おっ!ぱい API>=28 + * @return + */ + public static boolean isPie() { + return check((Build.VERSION_CODES.P)); + } + + /** + * おっ!ぱい API>=28 + * @return + */ + public static boolean isAndroid9() { + return check((Build.VERSION_CODES.P)); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/FpsCounter.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/FpsCounter.java new file mode 100644 index 0000000000..a60d4f770b --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/FpsCounter.java @@ -0,0 +1,44 @@ +package com.serenegiant.utils; + +public class FpsCounter { + private int cnt, prevCnt; + private long startTime, prevTime; + private float fps, totalFps; + public FpsCounter() { + reset(); + } + + public synchronized FpsCounter reset() { + cnt = prevCnt = 0; + startTime = prevTime = Time.nanoTime() - 1; + return this; + } + + /** + * フレームをカウント + */ + public synchronized void count() { + cnt++; + } + + /** + * FPSの値を更新, 1秒程度毎に呼び出す + * @return + */ + public synchronized FpsCounter update() { + final long t = Time.nanoTime(); + fps = (cnt - prevCnt) * 1000000000.0f / (t - prevTime); + prevCnt = cnt; + prevTime = t; + totalFps = cnt * 1000000000.0f / (t - startTime); + return this; + } + + public synchronized float getFps() { + return fps; + } + + public synchronized float getTotalFps() { + return totalFps; + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/HandlerThreadHandler.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/HandlerThreadHandler.java new file mode 100644 index 0000000000..81dcd39159 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/HandlerThreadHandler.java @@ -0,0 +1,41 @@ +package com.serenegiant.utils; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class HandlerThreadHandler extends Handler { + private static final String TAG = "HandlerThreadHandler"; + + public static final HandlerThreadHandler createHandler() { + return createHandler(TAG); + } + + public static final HandlerThreadHandler createHandler(final String name) { + final HandlerThread thread = new HandlerThread(name); + thread.start(); + return new HandlerThreadHandler(thread.getLooper()); + } + + public static final HandlerThreadHandler createHandler(@Nullable final Callback callback) { + return createHandler(TAG, callback); + } + + public static final HandlerThreadHandler createHandler(final String name, @Nullable final Callback callback) { + final HandlerThread thread = new HandlerThread(name); + thread.start(); + return new HandlerThreadHandler(thread.getLooper(), callback); + } + + private HandlerThreadHandler(@NonNull final Looper looper) { + super(looper); + } + + private HandlerThreadHandler(@NonNull final Looper looper, @Nullable final Callback callback) { + super(looper, callback); + } + +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/MessageTask.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/MessageTask.java new file mode 100644 index 0000000000..852d432025 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/MessageTask.java @@ -0,0 +1,515 @@ +package com.serenegiant.utils; + +import android.util.Log; + +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; + +public abstract class MessageTask implements Runnable { +// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること + private static final String TAG = MessageTask.class.getSimpleName(); + + public static class TaskBreak extends RuntimeException { + } + + protected static final class Request { + int request; + int arg1; + int arg2; + Object obj; + int request_for_result; + Object result; + + private Request() { + request = request_for_result = REQUEST_TASK_NON; + } + + /** + * @param _request minus value is reserved internal use + * @param _arg1 + * @param _arg2 + * @param _obj + */ + public Request(final int _request, final int _arg1, final int _arg2, final Object _obj) { + request = _request; + arg1 = _arg1; + arg2 = _arg2; + obj = _obj; + request_for_result = REQUEST_TASK_NON; + } + + public void setResult(final Object result) { + synchronized (this) { + this.result = result; + request = request_for_result = REQUEST_TASK_NON; + notifyAll(); + } + } + + @Override + public boolean equals(final Object o) { + return (o instanceof Request) + ? (request == ((Request) o).request) + && (request_for_result == ((Request)o).request_for_result) + && (arg1 == ((Request) o).arg1) + && (arg2 == ((Request) o).arg2) + && (obj == ((Request) o).obj) + : super.equals(o); + } + } + + // minus values and zero are reserved for internal use + protected static final int REQUEST_TASK_NON = 0; + protected static final int REQUEST_TASK_RUN = -1; + protected static final int REQUEST_TASK_RUN_AND_WAIT = -2; + protected static final int REQUEST_TASK_START = -8; + protected static final int REQUEST_TASK_QUIT = -9; + + private final Object mSync = new Object(); + /** プール/キューのサイズ, -1なら無制限 */ + private final int mMaxRequest; + private final LinkedBlockingQueue mRequestPool; // FIXME これはArrayListにした方が速いかも + private final LinkedBlockingDeque mRequestQueue; + private volatile boolean mIsRunning, mFinished; + private Thread mWorkerThread; + + /** + * コンストラクタ + * プール&キューのサイズは無制限 + * プールは空で生成 + */ + public MessageTask() { + mMaxRequest = -1; + mRequestPool = new LinkedBlockingQueue(); + mRequestQueue = new LinkedBlockingDeque(); + } + + /** + * コンストラクタ + * プール&キューのサイズは無制限 + * @param init_num プールするRequestの初期数を指定 + */ + public MessageTask(final int init_num) { + mMaxRequest = -1; + mRequestPool = new LinkedBlockingQueue(); + mRequestQueue = new LinkedBlockingDeque(); + for (int i = 0; i < init_num; i++) { + if (!mRequestPool.offer(new Request())) break; + } + } + + /** + * コンストラクタ + * プール及びキュー可能な最大サイズを指定して初期化 + * @param max_request キューの最大サイズを指定 + * @param init_num プールするRequestの初期数を指定, max_requestよりも大きければ切り捨てる + */ + public MessageTask(final int max_request, final int init_num) { + mMaxRequest = max_request; + mRequestPool = new LinkedBlockingQueue(max_request); + mRequestQueue = new LinkedBlockingDeque(max_request); + for (int i = 0; i < init_num; i++) { + if (!mRequestPool.offer(new Request())) break; + } + } + + /** + * 初期化要求。継承クラスのコンストラクタから呼び出すこと + * パラメータはonInitに引き渡される + * @param arg1 + * @param arg2 + * @param obj + */ + protected void init(final int arg1, final int arg2, final Object obj) { + mFinished = false; + mRequestQueue.offer(obtain(REQUEST_TASK_START, arg1, arg2, obj)); +// offer(REQUEST_TASK_START, arg1, arg2, obj); + } + + /** 初期化処理 */ + protected abstract void onInit(final int arg1, final int arg2, final Object obj); + + /** 要求処理ループ開始直前に呼ばれる */ + protected abstract void onStart(); + + /** onStopの直前に呼び出される, interruptされた時は呼び出されない */ + protected void onBeforeStop() {} + + /** 停止処理, interruptされた時は呼び出されない */ + protected abstract void onStop(); + + /** onStop後に呼び出される。onStopで例外発生しても呼ばれる */ + protected abstract void onRelease(); + + /** + * メッセージ処理ループ中でのエラー発生時の処理 + * デフフォルトはtrueを返しメッセージ処理ループを終了する + * @return trueを返すとメッセージ処理ループを終了する + */ + protected boolean onError(final Exception e) { +// if (DEBUG) Log.w(TAG, e); + return true; + } + + /** 要求メッセージの処理(内部メッセージは来ない) + * TaskBreakをthrowすると要求メッセージ処理ループを終了する */ + protected abstract Object processRequest(final int request, final int arg1, final int arg2, final Object obj) throws TaskBreak; + + /** 要求メッセージを取り出す処理(要求メッセージがなければブロックされる) */ + protected Request takeRequest() throws InterruptedException { + return mRequestQueue.take(); + } + + public boolean waitReady() { + synchronized (mSync) { + for ( ; !mIsRunning && !mFinished ; ) { + try { + mSync.wait(500); + } catch (final InterruptedException e) { + break; + } + } + return mIsRunning; + } + } + + public boolean isRunning() { + return mIsRunning; + } + + public boolean isFinished() { + return mFinished; + } + + @Override + public void run() { + Request request = null; + mIsRunning = true; + try { + request = mRequestQueue.take(); + } catch (final InterruptedException e) { + mIsRunning = false; + mFinished = true; + } + synchronized (mSync) { + if (mIsRunning) { + mWorkerThread = Thread.currentThread(); + try { + onInit(request.arg1, request.arg2, request.obj); + } catch (final Exception e) { + Log.w(TAG, e); + mIsRunning = false; + mFinished = true; + } + } + mSync.notifyAll(); + } + if (mIsRunning) { + try { + onStart(); + } catch (final Exception e) { + if (callOnError(e)) { + mIsRunning = false; + mFinished = true; + } + } + } +LOOP: for (; mIsRunning; ) { + try { + request = takeRequest(); + switch (request.request) { + case REQUEST_TASK_NON: + break; + case REQUEST_TASK_QUIT: + break LOOP; + case REQUEST_TASK_RUN: + if (request.obj instanceof Runnable) + try { + ((Runnable)request.obj).run(); + } catch (final Exception e) { + if (callOnError(e)) + break LOOP; + } + break; + case REQUEST_TASK_RUN_AND_WAIT: + try { + request.setResult(processRequest(request.request_for_result, request.arg1, request.arg2, request.obj)); + } catch (final TaskBreak e) { + request.setResult(null); + break LOOP; + } catch (final Exception e) { + request.setResult(null); + if (callOnError(e)) + break LOOP; + } + break; + default: + try { + processRequest(request.request, request.arg1, request.arg2, request.obj); + } catch (final TaskBreak e) { + break LOOP; + } catch (final Exception e) { + if (callOnError(e)) + break LOOP; + } + break; + } + request.request = request.request_for_result = REQUEST_TASK_NON; + // プールへ返却する + mRequestPool.offer(request); + } catch (final InterruptedException e) { + break; + } + } + final boolean interrupted = Thread.interrupted(); + synchronized (mSync) { + mWorkerThread = null; + mIsRunning = false; + mFinished = true; + } + if (!interrupted) { + try { + onBeforeStop(); + onStop(); + } catch (final Exception e) { + callOnError(e); + } + } + try { + onRelease(); + } catch (final Exception e) { + // callOnError(e); + } + synchronized (mSync) { + mSync.notifyAll(); + } + } + + /** + * エラー処理。onErrorを呼び出す。 + * trueを返すと要求メッセージ処理ループを終了する + * @param e + * @return + */ + protected boolean callOnError(final Exception e) { + try { + return onError(e); + } catch (final Exception e2) { +// if (DEBUG) Log.e(TAG, "exception occurred in callOnError", e); + } + return true; + } + + /** + * RequestプールからRequestを取得する + * プールが空の場合は新規に生成する + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + * @param obj + * @return Request + */ + protected Request obtain(final int request, final int arg1, final int arg2, final Object obj) { + Request req = mRequestPool.poll(); + if (req != null) { + req.request = request; + req.arg1 = arg1; + req.arg2 = arg2; + req.obj = obj; + } else { + req = new Request(request, arg1, arg2, obj); + } + return req; + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + * @param obj + * @return true if success offer + */ + public boolean offer(final int request, final int arg1, final int arg2, final Object obj) { + return !mFinished && mRequestQueue.offer(obtain(request, arg1, arg2, obj)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @param obj + * @return true if success offer + */ + public boolean offer(final int request, final int arg1, final Object obj) { + return !mFinished && mRequestQueue.offer(obtain(request, arg1, 0, obj)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + * @return true if success offer + */ + public boolean offer(final int request, final int arg1, final int arg2) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, arg1, arg2, null)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param arg1 + * @return true if success offer + */ + public boolean offer(final int request, final int arg1) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, arg1, 0, null)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @return true if success offer + */ + public boolean offer(final int request) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, 0, 0, null)); + } + + /** + * offer request to run on worker thread + * @param request minus values and zero are reserved + * @param obj + * @return true if success offer + */ + public boolean offer(final int request, final Object obj) { + return !mFinished && mIsRunning && mRequestQueue.offer(obtain(request, 0, 0, obj)); + } + + /** + * offer request to run on worker thread on top of the request queue + * @param request minus values and zero are reserved + * @param arg1 + * @param arg2 + */ + public boolean offerFirst(final int request, final int arg1, final int arg2, final Object obj) { + return !mFinished && mIsRunning && mRequestQueue.offerFirst(obtain(request, arg1, arg2, obj)); + } + + /** + * offer request to run on worker thread and wait for result + * caller thread is blocked until the request finished running on worker thread + * FIXME このメソッドはMessageTaskを実行中のスレッド上で呼び出すとデッドロックする + * @param request + * @param arg1 + * @param arg2 + * @param obj + * @return + */ + public Object offerAndWait(final int request, final int arg1, final int arg2, final Object obj) { + if (!mFinished && (request > REQUEST_TASK_NON)) { + final Request req = obtain(REQUEST_TASK_RUN_AND_WAIT, arg1, arg2, obj); + synchronized (req) { + req.request_for_result = request; + req.result = null; + mRequestQueue.offer(req); + for (; mIsRunning && (req.request_for_result != REQUEST_TASK_NON); ) { + try { + req.wait(100); + } catch (final InterruptedException e) { + break; + } + } + } + return req.result; + } else { + return null; + } + } + + /** + * request to run on worker thread + * @param task + * @return true if success queue + */ + public boolean queueEvent(final Runnable task) { + return !mFinished && (task != null) && offer(REQUEST_TASK_RUN, task); + } + + public void removeRequest(final Request request) { + for (final Request req: mRequestQueue) { + if (!mIsRunning || mFinished) break; + if (req.equals(request)) { + mRequestQueue.remove(req); + mRequestPool.offer(req); + } + } + } + + public void removeRequest(final int request) { + for (final Request req: mRequestQueue) { + if (!mIsRunning || mFinished) break; + if (req.request == request) { + mRequestQueue.remove(req); + mRequestPool.offer(req); + } + } + } + + /** + * request terminate worker thread and release all related resources + */ + public void release() { + release(false); + } + + /** + * request terminate worker thread and release all related resources + * @param interrupt trueなら実行中のタスクをinterruptする + */ + public void release(final boolean interrupt) { + final boolean b = mIsRunning; + mIsRunning = false; + if (!mFinished) { + mRequestQueue.clear(); + mRequestQueue.offerFirst(obtain(REQUEST_TASK_QUIT, 0, 0, null)); + synchronized (mSync) { + if (b) { + final long current = Thread.currentThread().getId(); + final long id = mWorkerThread != null ? mWorkerThread.getId() : current; + if (id != current) { + if (interrupt && (mWorkerThread != null)) { + mWorkerThread.interrupt(); + } + for ( ; !mFinished ; ) { + try { + mSync.wait(300); + } catch (final InterruptedException e) { + // ignore + } + } + } + } + } + } + } + + /** + * 実行中のタスクが終了後開放する + */ + public void releaseSelf() { + mIsRunning = false; + if (!mFinished) { + mRequestQueue.clear(); + mRequestQueue.offerFirst(obtain(REQUEST_TASK_QUIT, 0, 0, null)); + } + } + + /** + * processRequest内でメッセージループを非常終了させるためのヘルパーメソッド + * 単にTaskBreakをthrowするだけ + * @throws TaskBreak + */ + public void userBreak() throws TaskBreak { + throw new TaskBreak(); + } +} diff --git a/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/Time.java b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/Time.java new file mode 100644 index 0000000000..6d7ecb3894 --- /dev/null +++ b/libraries/map-usbcamera/src/main/java/com/serenegiant/utils/Time.java @@ -0,0 +1,40 @@ +package com.serenegiant.utils; + +import android.annotation.SuppressLint; +import android.os.SystemClock; + +public class Time { + + public static boolean prohibitElapsedRealtimeNanos = true; + + private static Time sTime; + static { + reset(); + } + + public static long nanoTime() { + return sTime.timeNs(); + } + + public static void reset() { + if (!prohibitElapsedRealtimeNanos && BuildCheck.isJellyBeanMr1()) { + sTime = new TimeJellyBeanMr1(); + } else { + sTime = new Time(); + } + } + + private Time() { + } + + @SuppressLint("NewApi") + private static class TimeJellyBeanMr1 extends Time { + public long timeNs() { + return SystemClock.elapsedRealtimeNanos(); + } + } + + protected long timeNs() { + return System.nanoTime(); + } +}