Merge branch 'dev_robobus-d_230322_3.0.0' into 'dev_robobus_network_weak_230323'
Dev robobus d 230322 3.0.0 See merge request zhjt/AndroidApp/MoGoEagleEye!743
This commit is contained in:
@@ -27,14 +27,19 @@ import com.mogo.eagle.core.data.map.MogoLocation;
|
||||
import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener;
|
||||
import com.mogo.eagle.core.function.api.autopilot.IMoGoChassisLocationGCJ02Listener;
|
||||
import com.mogo.eagle.core.function.api.autopilot.IMoGoPlanningRottingListener;
|
||||
import com.mogo.eagle.core.function.api.telematic.IReceivedMsgListener;
|
||||
import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager;
|
||||
import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationGCJ02ListenerManager;
|
||||
import com.mogo.eagle.core.function.call.autopilot.CallerPlanningRottingListenerManager;
|
||||
import com.mogo.eagle.core.function.call.telematic.CallerTelematicListenerManager;
|
||||
import com.mogo.eagle.core.network.utils.GsonUtil;
|
||||
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils;
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger;
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.Logger;
|
||||
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant;
|
||||
import com.mogo.eagle.core.utilcode.mogo.storage.SharedPrefsMgr;
|
||||
import com.mogo.eagle.core.utilcode.util.CoordinateUtils;
|
||||
import com.mogo.eagle.core.utilcode.util.GsonUtils;
|
||||
import com.mogo.eagle.core.utilcode.util.NetworkUtils;
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils;
|
||||
import com.mogo.och.bus.passenger.R;
|
||||
@@ -50,9 +55,13 @@ import com.mogo.och.bus.passenger.callback.IBusPassengerRouteLineInfoCallback;
|
||||
import com.mogo.och.bus.passenger.constant.BusPassengerConst;
|
||||
import com.mogo.och.bus.passenger.network.BusPassengerModelLoopManager;
|
||||
import com.mogo.och.bus.passenger.network.BusPassengerServiceManager;
|
||||
import com.mogo.och.common.module.bean.AppConnectMsg;
|
||||
import com.mogo.och.common.module.biz.common.socketmessage.OCHSocketMessageManager;
|
||||
import com.mogo.och.common.module.biz.constant.OchCommonConst;
|
||||
import com.mogo.och.common.module.biz.network.OchCommonServiceCallback;
|
||||
import com.mogo.och.common.module.manager.AbnormalFactorsLoopManager;
|
||||
import com.mogo.och.common.module.utils.CoordinateCalculateRouteUtil;
|
||||
import com.mogo.och.common.module.utils.DateTimeUtil;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -283,6 +292,9 @@ public class BusPassengerModel {
|
||||
//2021.11.1 自动驾驶路线规划接口
|
||||
CallerPlanningRottingListenerManager.INSTANCE.addListener(TAG,moGoAutopilotPlanningListener);
|
||||
|
||||
//监听司机端消息
|
||||
CallerTelematicListenerManager.INSTANCE.addListener(TAG,mReceivedMsgListener);
|
||||
|
||||
AbnormalFactorsLoopManager.INSTANCE.startLoopAbnormalFactors(mContext);
|
||||
}
|
||||
|
||||
@@ -297,10 +309,27 @@ public class BusPassengerModel {
|
||||
|
||||
CallerAutoPilotStatusListenerManager.INSTANCE.removeListener(mGoAutopilotStatusListener);
|
||||
CallerPlanningRottingListenerManager.INSTANCE.removeListener(moGoAutopilotPlanningListener);
|
||||
CallerTelematicListenerManager.INSTANCE.removeListener(TAG);
|
||||
|
||||
AbnormalFactorsLoopManager.INSTANCE.stopLoopAbnormalFactors();
|
||||
}
|
||||
|
||||
private final IReceivedMsgListener mReceivedMsgListener = new IReceivedMsgListener() {
|
||||
@Override
|
||||
public void onReceivedMsg(int type, @NonNull byte[] byteArray) {
|
||||
if (OchCommonConst.BUSINESS_STRING == type){
|
||||
|
||||
AppConnectMsg msg = GsonUtils.fromJson(new String(byteArray), AppConnectMsg.class);
|
||||
Logger.d(SceneConstant.M_BUS_P+TAG,"onReceivedMsg = "+GsonUtils.toJson(msg));
|
||||
if (msg.isViewShow()){ //消息盒子显示内容
|
||||
OCHSocketMessageManager.INSTANCE.pushAppOperationalMsgBox(
|
||||
DateTimeUtil.getCurrentTimeStamp(),msg.getMsg(),
|
||||
OCHSocketMessageManager.OPERATION_SYSTEM);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//监听网络变化,避免启动机器时无网导致无法更新订单信息
|
||||
private final IMogoIntentListener mNetWorkIntentListener = new IMogoIntentListener() {
|
||||
@Override
|
||||
|
||||
@@ -328,8 +328,6 @@ dependencies {
|
||||
implementation rootProject.ext.dependencies.android_start_up
|
||||
implementation rootProject.ext.dependencies.lancetx_runtime
|
||||
implementation rootProject.ext.dependencies.mogocustommap
|
||||
implementation project(':libraries:map-usbcamera')
|
||||
|
||||
implementation project(':core:function-impl:mogo-core-function-startup')
|
||||
implementation project(':core:function-impl:mogo-core-function-devatools')
|
||||
implementation project(':core:function-impl:mogo-core-function-datacenter')
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Message
|
||||
import android.util.Log
|
||||
import com.mogo.aicloud.services.socket.IMogoOnMessageListener
|
||||
import com.mogo.aicloud.services.socket.MogoAiCloudSocketManager
|
||||
import com.mogo.commons.voice.AIAssist
|
||||
import com.mogo.eagle.core.data.autopilot.AutopilotControlParameters
|
||||
import com.mogo.eagle.core.data.autopilot.AutopilotRouteInfo
|
||||
import com.mogo.eagle.core.data.biz.dispatch.DispatchAdasAutoPilotLocReceiverBean
|
||||
@@ -183,6 +184,10 @@ class DispatchAutoPilotManager private constructor() :
|
||||
)
|
||||
}
|
||||
}
|
||||
val currentAutoPilotLine = AutopilotControlParameters.AutoPilotLine(it.lineId.toLong(),it.trajUrl,it.trajMd5,
|
||||
it.stopUrl,it.stopMd5,it.timestamp,it.vehicleModel,it.trajUrl_dpqp,it.trajMd5_dpqp,it.stopUrl_dpqp,
|
||||
it.stopMd5_dpqp,it.timestamp_dpqp)
|
||||
currentAutopilot.autoPilotLine = currentAutoPilotLine
|
||||
currentAutopilot.routeID = it.lineId
|
||||
currentAutopilot.routeName = it.lineName
|
||||
currentAutopilot.startName = it.startLocAddress
|
||||
@@ -310,6 +315,10 @@ class DispatchAutoPilotManager private constructor() :
|
||||
//调度完成,同步云端,并且吐司提示
|
||||
dispatchServiceModel.dispatchResultUpload(DISPATCH_COMPLETED, it.taskId)
|
||||
ToastUtils.showLong("云调度完成,车辆即将到达${it.endLocAddress}")
|
||||
//取消自动驾驶
|
||||
CallerAutoPilotControlManager.cancelAutoPilot()
|
||||
//语音提示到站
|
||||
AIAssist.getInstance(mContext).speakTTSVoice("云调度完成,车辆即将到达${it.endLocAddress}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
libraries/map-usbcamera/.gitignore
vendored
1
libraries/map-usbcamera/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,73 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
id 'com.alibaba.arouter'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
// buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode Integer.valueOf(VERSION_CODE)
|
||||
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
|
||||
//ARouter apt 参数
|
||||
kapt {
|
||||
useBuildCache = false
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
||||
sourceSets{
|
||||
main{
|
||||
jniLibs.srcDir(['libs'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
||||
implementation rootProject.ext.dependencies.androidxconstraintlayout
|
||||
implementation rootProject.ext.dependencies.arouter
|
||||
kapt rootProject.ext.dependencies.aroutercompiler
|
||||
|
||||
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
@@ -1,3 +0,0 @@
|
||||
GROUP=com.mogo.camera
|
||||
POM_ARTIFACT_ID=map-usbcamera
|
||||
VERSION_CODE=1
|
||||
Binary file not shown.
21
libraries/map-usbcamera/proguard-rules.pro
vendored
21
libraries/map-usbcamera/proguard-rules.pro
vendored
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,13 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.usbcamera">
|
||||
|
||||
|
||||
<!-- 声明使用usb -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="true" />
|
||||
|
||||
<application>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
package com.mogo.usbcamera;
|
||||
|
||||
import com.serenegiant.usb.USBMonitor;
|
||||
|
||||
/**
|
||||
* @author donghongyu
|
||||
* USB摄像头方案,获取权限、获取YUV数据
|
||||
*/
|
||||
public class USBCameraHelper {
|
||||
private static USBCameraHelper mCameraHelper;
|
||||
/**
|
||||
* USB Manager
|
||||
*/
|
||||
private USBMonitor mUSBMonitor;
|
||||
|
||||
private USBCameraHelper() {
|
||||
}
|
||||
|
||||
public static USBCameraHelper getInstance() {
|
||||
if (mCameraHelper == null) {
|
||||
mCameraHelper = new USBCameraHelper();
|
||||
}
|
||||
return mCameraHelper;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void registerUSB() {
|
||||
if (mUSBMonitor != null) {
|
||||
mUSBMonitor.register();
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterUSB() {
|
||||
if (mUSBMonitor != null) {
|
||||
mUSBMonitor.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
package com.mogo.usbcamera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Environment;
|
||||
|
||||
import com.serenegiant.usb.DeviceFilter;
|
||||
import com.serenegiant.usb.Size;
|
||||
import com.serenegiant.usb.USBMonitor;
|
||||
import com.serenegiant.usb.UVCCamera;
|
||||
import com.serenegiant.usb.common.AbstractUVCCameraHandler;
|
||||
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
|
||||
*/
|
||||
public class UVCCameraHelper {
|
||||
public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
+ File.separator;
|
||||
public static final String SUFFIX_JPEG = ".jpg";
|
||||
public static final String SUFFIX_MP4 = ".mp4";
|
||||
private static final String TAG = "UVCCameraHelper";
|
||||
private int previewWidth = 640;
|
||||
private int previewHeight = 480;
|
||||
public static final int FRAME_FORMAT_YUYV = UVCCamera.FRAME_FORMAT_YUYV;
|
||||
// Default using MJPEG
|
||||
// if your device is connected,but have no images
|
||||
// please try to change it to FRAME_FORMAT_YUYV
|
||||
public static final int FRAME_FORMAT_MJPEG = UVCCamera.FRAME_FORMAT_MJPEG;
|
||||
public static final int MODE_BRIGHTNESS = UVCCamera.PU_BRIGHTNESS;
|
||||
public static final int MODE_CONTRAST = UVCCamera.PU_CONTRAST;
|
||||
private int mFrameFormat = FRAME_FORMAT_MJPEG;
|
||||
|
||||
private static UVCCameraHelper mCameraHelper;
|
||||
// USB Manager
|
||||
private USBMonitor mUSBMonitor;
|
||||
// Camera Handler
|
||||
private UVCCameraHandler mCameraHandler;
|
||||
private USBMonitor.UsbControlBlock mCtrlBlock;
|
||||
|
||||
private Activity mActivity;
|
||||
private CameraViewInterface mCamView;
|
||||
|
||||
private UVCCameraHelper() {
|
||||
}
|
||||
|
||||
public static UVCCameraHelper getInstance() {
|
||||
if (mCameraHelper == null) {
|
||||
mCameraHelper = new UVCCameraHelper();
|
||||
}
|
||||
return mCameraHelper;
|
||||
}
|
||||
|
||||
public void closeCamera() {
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.close();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnMyDevConnectListener {
|
||||
void onAttachDev(UsbDevice device);
|
||||
|
||||
void onDettachDev(UsbDevice device);
|
||||
|
||||
void onConnectDev(UsbDevice device, boolean isConnected);
|
||||
|
||||
void onDisConnectDev(UsbDevice device);
|
||||
|
||||
void onCancelDev(UsbDevice device);
|
||||
|
||||
}
|
||||
|
||||
public void initUSBMonitor(Activity activity, CameraViewInterface cameraView, final OnMyDevConnectListener listener) {
|
||||
this.mActivity = activity;
|
||||
this.mCamView = cameraView;
|
||||
|
||||
mUSBMonitor = new USBMonitor(activity.getApplicationContext(), new USBMonitor.OnDeviceConnectListener() {
|
||||
|
||||
// called by checking usb device
|
||||
// do request device permission
|
||||
@Override
|
||||
public void onAttach(UsbDevice device) {
|
||||
if (listener != null) {
|
||||
listener.onAttachDev(device);
|
||||
}
|
||||
}
|
||||
|
||||
// called by taking out usb device
|
||||
// do close camera
|
||||
@Override
|
||||
public void onDettach(UsbDevice device) {
|
||||
if (listener != null) {
|
||||
listener.onDettachDev(device);
|
||||
}
|
||||
}
|
||||
|
||||
// called by connect to usb camera
|
||||
// do open camera,start previewing
|
||||
@Override
|
||||
public void onConnect(final UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) {
|
||||
mCtrlBlock = ctrlBlock;
|
||||
openCamera(ctrlBlock);
|
||||
new Thread(() -> {
|
||||
// wait for camera created
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// start previewing
|
||||
startPreview(mCamView);
|
||||
}).start();
|
||||
if (listener != null) {
|
||||
listener.onConnectDev(device, true);
|
||||
}
|
||||
}
|
||||
|
||||
// called by disconnect to usb camera
|
||||
// do nothing
|
||||
@Override
|
||||
public void onDisconnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock) {
|
||||
if (listener != null) {
|
||||
listener.onDisConnectDev(device);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(UsbDevice device) {
|
||||
if (listener != null) {
|
||||
listener.onCancelDev(device);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
createUVCCamera();
|
||||
}
|
||||
|
||||
public void createUVCCamera() {
|
||||
if (mCamView == null) {
|
||||
throw new NullPointerException("CameraViewInterface cannot be null!");
|
||||
}
|
||||
|
||||
// release resources for initializing camera handler
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.release();
|
||||
mCameraHandler = null;
|
||||
}
|
||||
// initialize camera handler
|
||||
mCamView.setAspectRatio(previewWidth / (float) previewHeight);
|
||||
mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2,
|
||||
previewWidth, previewHeight, mFrameFormat);
|
||||
}
|
||||
|
||||
public void updateResolution(int width, int height) {
|
||||
if (previewWidth == width && previewHeight == height) {
|
||||
return;
|
||||
}
|
||||
this.previewWidth = width;
|
||||
this.previewHeight = height;
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.release();
|
||||
mCameraHandler = null;
|
||||
}
|
||||
mCamView.setAspectRatio(previewWidth / (float) previewHeight);
|
||||
mCameraHandler = UVCCameraHandler.createHandler(mActivity, mCamView, 2,
|
||||
previewWidth, previewHeight, mFrameFormat);
|
||||
openCamera(mCtrlBlock);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// wait for camera created
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// start previewing
|
||||
startPreview(mCamView);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void registerUSB() {
|
||||
if (mUSBMonitor != null) {
|
||||
mUSBMonitor.register();
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterUSB() {
|
||||
if (mUSBMonitor != null) {
|
||||
mUSBMonitor.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkSupportFlag(final int flag) {
|
||||
return mCameraHandler != null && mCameraHandler.checkSupportFlag(flag);
|
||||
}
|
||||
|
||||
public int getModelValue(final int flag) {
|
||||
return mCameraHandler != null ? mCameraHandler.getValue(flag) : 0;
|
||||
}
|
||||
|
||||
public int setModelValue(final int flag, final int value) {
|
||||
return mCameraHandler != null ? mCameraHandler.setValue(flag, value) : 0;
|
||||
}
|
||||
|
||||
public int resetModelValue(final int flag) {
|
||||
return mCameraHandler != null ? mCameraHandler.resetValue(flag) : 0;
|
||||
}
|
||||
|
||||
public void requestPermission(int index) {
|
||||
List<UsbDevice> devList = getUsbDeviceList();
|
||||
if (devList == null || devList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
int count = devList.size();
|
||||
if (index >= count) {
|
||||
new IllegalArgumentException("index illegal,should be < devList.size()");
|
||||
}
|
||||
if (mUSBMonitor != null) {
|
||||
mUSBMonitor.requestPermission(getUsbDeviceList().get(index));
|
||||
}
|
||||
}
|
||||
|
||||
public int getUsbDeviceCount() {
|
||||
List<UsbDevice> devList = getUsbDeviceList();
|
||||
if (devList == null || devList.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return devList.size();
|
||||
}
|
||||
|
||||
public List<UsbDevice> getUsbDeviceList() {
|
||||
List<DeviceFilter> deviceFilters = DeviceFilter
|
||||
.getDeviceFilters(mActivity.getApplicationContext(), R.xml.device_filter);
|
||||
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) {
|
||||
if (mCameraHandler != null && mCameraHandler.isOpened()) {
|
||||
File file = new File(savePath);
|
||||
if (!Objects.requireNonNull(file.getParentFile()).exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
}
|
||||
mCameraHandler.captureStill(savePath, listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void startPusher(AbstractUVCCameraHandler.OnEncodeResultListener listener) {
|
||||
if (mCameraHandler != null && !isPushing()) {
|
||||
mCameraHandler.startRecording(null, listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void startPusher(RecordParams params, AbstractUVCCameraHandler.OnEncodeResultListener listener) {
|
||||
if (mCameraHandler != null && !isPushing()) {
|
||||
if (params.isSupportOverlay()) {
|
||||
TxtOverlay.install(mActivity.getApplicationContext());
|
||||
}
|
||||
mCameraHandler.startRecording(params, listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopPusher() {
|
||||
if (mCameraHandler != null && isPushing()) {
|
||||
mCameraHandler.stopRecording();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPushing() {
|
||||
if (mCameraHandler != null) {
|
||||
return mCameraHandler.isRecording();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCameraOpened() {
|
||||
if (mCameraHandler != null) {
|
||||
return mCameraHandler.isOpened();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.release();
|
||||
mCameraHandler = null;
|
||||
}
|
||||
if (mUSBMonitor != null) {
|
||||
mUSBMonitor.destroy();
|
||||
mUSBMonitor = null;
|
||||
}
|
||||
}
|
||||
|
||||
public USBMonitor getUSBMonitor() {
|
||||
return mUSBMonitor;
|
||||
}
|
||||
|
||||
public void setOnPreviewFrameListener(AbstractUVCCameraHandler.OnPreViewResultListener listener) {
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.setOnPreViewResultListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void openCamera(USBMonitor.UsbControlBlock ctrlBlock) {
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.open(ctrlBlock);
|
||||
}
|
||||
}
|
||||
|
||||
public void startPreview(CameraViewInterface cameraView) {
|
||||
SurfaceTexture st = cameraView.getSurfaceTexture();
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.startPreview(st);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopPreview() {
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
public void startCameraFoucs() {
|
||||
if (mCameraHandler != null) {
|
||||
mCameraHandler.startCameraFoucs();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Size> getSupportedPreviewSizes() {
|
||||
if (mCameraHandler == null) {
|
||||
return null;
|
||||
}
|
||||
return mCameraHandler.getSupportedPreviewSizes();
|
||||
}
|
||||
|
||||
public void setDefaultPreviewSize(int defaultWidth, int defaultHeight) {
|
||||
if (mUSBMonitor != null) {
|
||||
throw new IllegalStateException("setDefaultPreviewSize should be call before initMonitor");
|
||||
}
|
||||
this.previewWidth = defaultWidth;
|
||||
this.previewHeight = defaultHeight;
|
||||
}
|
||||
|
||||
public void setDefaultFrameFormat(int format) {
|
||||
if (mUSBMonitor != null) {
|
||||
throw new IllegalStateException("setDefaultFrameFormat should be call before initMonitor");
|
||||
}
|
||||
this.mFrameFormat = format;
|
||||
}
|
||||
|
||||
public int getPreviewWidth() {
|
||||
return previewWidth;
|
||||
}
|
||||
|
||||
public int getPreviewHeight() {
|
||||
return previewHeight;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.mogo.usbcamera.utils;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* Created by jiangdongguo on 2017/10/18.
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
private static BufferedOutputStream outputStream;
|
||||
public static String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator;
|
||||
|
||||
public static void createfile(String path){
|
||||
File file = new File(path);
|
||||
if(file.exists()){
|
||||
file.delete();
|
||||
}
|
||||
try {
|
||||
outputStream = new BufferedOutputStream(new FileOutputStream(file));
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void releaseFile(){
|
||||
try {
|
||||
if(outputStream != null) {
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void putFileStream(byte[] data,int offset,int length){
|
||||
if(outputStream != null) {
|
||||
try {
|
||||
outputStream.write(data,offset,length);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void putFileStream(byte[] data){
|
||||
if(outputStream != null) {
|
||||
try {
|
||||
outputStream.write(data);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.serenegiant.dialog;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
public abstract class DialogFragmentEx extends DialogFragment {
|
||||
protected static final String ARGS_KEY_REQUEST_CODE = "requestCode";
|
||||
protected static final String ARGS_KEY_ID_TITLE = "title";
|
||||
protected static final String ARGS_KEY_ID_MESSAGE = "message";
|
||||
protected static final String ARGS_KEY_TAG = "tag";
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
final Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
outState.putAll(args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package com.serenegiant.dialog;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.serenegiant.utils.BuildCheck;
|
||||
|
||||
/**
|
||||
* パーミッション要求前に説明用のダイアログを表示するためのDialogFragment
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public class MessageDialogFragment extends DialogFragment {
|
||||
// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること
|
||||
private static final String TAG = MessageDialogFragment.class.getSimpleName();
|
||||
|
||||
public static interface MessageDialogListener {
|
||||
public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result);
|
||||
}
|
||||
|
||||
public static MessageDialogFragment showDialog(final Activity parent, final int requestCode, final int id_title, final int id_message, final String[] permissions) {
|
||||
final MessageDialogFragment dialog = newInstance(requestCode, id_title, id_message, permissions);
|
||||
dialog.show(parent.getFragmentManager(), TAG);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static MessageDialogFragment showDialog(final Fragment parent, final int requestCode, final int id_title, final int id_message, final String[] permissions) {
|
||||
final MessageDialogFragment dialog = newInstance(requestCode, id_title, id_message, permissions);
|
||||
dialog.setTargetFragment(parent, parent.getId());
|
||||
dialog.show(parent.getFragmentManager(), TAG);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static MessageDialogFragment newInstance(final int requestCode, final int id_title, final int id_message, final String[] permissions) {
|
||||
final MessageDialogFragment fragment = new MessageDialogFragment();
|
||||
final Bundle args = new Bundle();
|
||||
// ここでパラメータをセットする
|
||||
args.putInt("requestCode", requestCode);
|
||||
args.putInt("title", id_title);
|
||||
args.putInt("message", id_message);
|
||||
args.putStringArray("permissions", permissions != null ? permissions : new String[]{});
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private MessageDialogListener mDialogListener;
|
||||
|
||||
public MessageDialogFragment() {
|
||||
super();
|
||||
// デフォルトコンストラクタが必要
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onAttach(final Activity activity) {
|
||||
super.onAttach(activity);
|
||||
// コールバックインターフェースを取得
|
||||
if (activity instanceof MessageDialogListener) {
|
||||
mDialogListener = (MessageDialogListener)activity;
|
||||
}
|
||||
if (mDialogListener == null) {
|
||||
final Fragment fragment = getTargetFragment();
|
||||
if (fragment instanceof MessageDialogListener) {
|
||||
mDialogListener = (MessageDialogListener)fragment;
|
||||
}
|
||||
}
|
||||
if (mDialogListener == null) {
|
||||
if (BuildCheck.isAndroid4_2()) {
|
||||
final Fragment target = getParentFragment();
|
||||
if (target instanceof MessageDialogListener) {
|
||||
mDialogListener = (MessageDialogListener)target;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mDialogListener == null) {
|
||||
// Log.w(TAG, "caller activity/fragment must implement PermissionDetailDialogFragmentListener");
|
||||
throw new ClassCastException(activity.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onCreate(final Bundle savedInstanceState) {
|
||||
// super.onCreate(savedInstanceState);
|
||||
// final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
final int requestCode = getArguments().getInt("requestCode");
|
||||
final int id_title = getArguments().getInt("title");
|
||||
final int id_message = getArguments().getInt("message");
|
||||
final String[] permissions = args.getStringArray("permissions");
|
||||
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(id_title)
|
||||
.setMessage(id_message)
|
||||
.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialog, final int whichButton) {
|
||||
// 本当はここでパーミッション要求をしたいだけどこのダイアログがdismissしてしまって結果を受け取れないので
|
||||
// 呼び出し側へ返してそこでパーミッション要求する。なのでこのダイアログは単にメッセージを表示するだけ
|
||||
try {
|
||||
mDialogListener.onMessageDialogResult(MessageDialogFragment.this, requestCode, permissions, true);
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialog, int whichButton) {
|
||||
try {
|
||||
mDialogListener.onMessageDialogResult(MessageDialogFragment.this, requestCode, permissions, false);
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package com.serenegiant.dialog;
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.serenegiant.utils.BuildCheck;
|
||||
|
||||
public class MessageDialogFragmentV4 extends DialogFragmentEx {
|
||||
// private static final boolean DEBUG = false; // FIXME 実働時はfalseにすること
|
||||
private static final String TAG = MessageDialogFragmentV4.class.getSimpleName();
|
||||
|
||||
private static final String ARGS_KEY_PERMISSIONS = "permissions";
|
||||
|
||||
/**
|
||||
* ダイアログの表示結果を受け取るためのコールバックリスナー
|
||||
*/
|
||||
public static interface MessageDialogListener {
|
||||
public void onMessageDialogResult(
|
||||
@NonNull final MessageDialogFragmentV4 dialog, final int requestCode,
|
||||
@NonNull final String[] permissions, final boolean result);
|
||||
}
|
||||
|
||||
/**
|
||||
* ダイアログ表示のためのヘルパーメソッド
|
||||
* @param parent
|
||||
* @param requestCode
|
||||
* @param id_title
|
||||
* @param id_message
|
||||
* @param permissions
|
||||
* @return
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
public static MessageDialogFragmentV4 showDialog(
|
||||
@NonNull final FragmentActivity parent, final int requestCode,
|
||||
@StringRes final int id_title, @StringRes final int id_message,
|
||||
@NonNull final String[] permissions) throws IllegalStateException {
|
||||
|
||||
final MessageDialogFragmentV4 dialog
|
||||
= newInstance(requestCode, id_title, id_message, permissions);
|
||||
dialog.show(parent.getSupportFragmentManager(), TAG);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* ダイアログ表示のためのヘルパーメソッド
|
||||
* @param parent
|
||||
* @param requestCode
|
||||
* @param id_title
|
||||
* @param id_message
|
||||
* @param permissions
|
||||
* @return
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
public static MessageDialogFragmentV4 showDialog(
|
||||
@NonNull final Fragment parent, final int requestCode,
|
||||
@StringRes final int id_title, @StringRes final int id_message,
|
||||
@NonNull final String[] permissions) throws IllegalStateException {
|
||||
|
||||
final MessageDialogFragmentV4 dialog
|
||||
= newInstance(requestCode, id_title, id_message, permissions);
|
||||
dialog.setTargetFragment(parent, parent.getId());
|
||||
dialog.show(parent.requireFragmentManager(), TAG);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* ダイアログ生成のためのヘルパーメソッド
|
||||
* ダイアログ自体を直接生成せずにこのメソッドを呼び出すこと
|
||||
* @param requestCode
|
||||
* @param id_title
|
||||
* @param id_message
|
||||
* @param permissions
|
||||
* @return
|
||||
*/
|
||||
public static MessageDialogFragmentV4 newInstance(
|
||||
final int requestCode,
|
||||
@StringRes final int id_title, @StringRes final int id_message,
|
||||
@NonNull final String[] permissions) {
|
||||
|
||||
final MessageDialogFragmentV4 fragment = new MessageDialogFragmentV4();
|
||||
final Bundle args = new Bundle();
|
||||
// ここでパラメータをセットする
|
||||
args.putInt(ARGS_KEY_REQUEST_CODE, requestCode);
|
||||
args.putInt(ARGS_KEY_ID_TITLE, id_title);
|
||||
args.putInt(ARGS_KEY_ID_MESSAGE, id_message);
|
||||
args.putStringArray(ARGS_KEY_PERMISSIONS, permissions);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private MessageDialogListener mDialogListener;
|
||||
|
||||
/**
|
||||
* コンストラクタ, 直接生成せずに#newInstanceを使うこと
|
||||
*/
|
||||
public MessageDialogFragmentV4() {
|
||||
super();
|
||||
// デフォルトコンストラクタが必要
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
// コールバックインターフェースを取得
|
||||
if (context instanceof MessageDialogListener) {
|
||||
mDialogListener = (MessageDialogListener)context;
|
||||
}
|
||||
if (mDialogListener == null) {
|
||||
final Fragment fragment = getTargetFragment();
|
||||
if (fragment instanceof MessageDialogListener) {
|
||||
mDialogListener = (MessageDialogListener)fragment;
|
||||
}
|
||||
}
|
||||
if (mDialogListener == null) {
|
||||
if (BuildCheck.isAndroid4_2()) {
|
||||
final Fragment target = getParentFragment();
|
||||
if (target instanceof MessageDialogListener) {
|
||||
mDialogListener = (MessageDialogListener)target;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mDialogListener == null) {
|
||||
// Log.w(TAG, "caller activity/fragment must implement PermissionDetailDialogFragmentListener");
|
||||
throw new ClassCastException(context.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onCreate(final Bundle savedInstanceState) {
|
||||
// super.onCreate(savedInstanceState);
|
||||
// final Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
// }
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
final Bundle args = savedInstanceState != null ? savedInstanceState : requireArguments();
|
||||
final int id_title = args.getInt(ARGS_KEY_ID_TITLE);
|
||||
final int id_message = args.getInt(ARGS_KEY_ID_MESSAGE);
|
||||
|
||||
final Activity activity = requireActivity();
|
||||
return new AlertDialog.Builder(activity)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(id_title)
|
||||
.setMessage(id_message)
|
||||
.setPositiveButton(android.R.string.ok, mOnClickListener)
|
||||
.setNegativeButton(android.R.string.cancel, mOnClickListener)
|
||||
.create();
|
||||
}
|
||||
|
||||
private final DialogInterface.OnClickListener mOnClickListener
|
||||
= new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialog, final int which) {
|
||||
// 本当はここでパーミッション要求をしたいだけどこのダイアログがdismissしてしまって結果を受け取れないので
|
||||
// 呼び出し側へ返してそこでパーミッション要求する。なのでこのダイアログは単にメッセージを表示するだけ
|
||||
callOnMessageDialogResult(which == DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCancel(final DialogInterface dialog) {
|
||||
super.onCancel(dialog);
|
||||
callOnMessageDialogResult(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* コールバックリスナー呼び出しのためのヘルパーメソッド
|
||||
* @param result
|
||||
*/
|
||||
private void callOnMessageDialogResult(final boolean result)
|
||||
throws IllegalStateException {
|
||||
|
||||
final Bundle args = requireArguments();
|
||||
final int requestCode = args.getInt(ARGS_KEY_REQUEST_CODE);
|
||||
final String[] permissions = args.getStringArray(ARGS_KEY_PERMISSIONS);
|
||||
try {
|
||||
mDialogListener.onMessageDialogResult(
|
||||
MessageDialogFragmentV4.this,
|
||||
requestCode, permissions, result);
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,248 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,724 +0,0 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -1,631 +0,0 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -1,548 +0,0 @@
|
||||
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<float[]> mParams = new SparseArray<float[]>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
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<br>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
/*
|
||||
* 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<RendererSurfaceRec> mClients
|
||||
= new SparseArray<RendererSurfaceRec>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,535 +0,0 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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];
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.Spinner;
|
||||
|
||||
|
||||
import com.mogo.usbcamera.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CameraDialog extends DialogFragment {
|
||||
private static final String TAG = CameraDialog.class.getSimpleName();
|
||||
|
||||
public interface CameraDialogParent {
|
||||
public USBMonitor getUSBMonitor();
|
||||
public void onDialogResult(boolean canceled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method
|
||||
* @param parent FragmentActivity
|
||||
* @return
|
||||
*/
|
||||
public static CameraDialog showDialog(final Activity parent/* add parameters here if you need */) {
|
||||
CameraDialog dialog = newInstance(/* add parameters here if you need */);
|
||||
try {
|
||||
dialog.show(parent.getFragmentManager(), TAG);
|
||||
} catch (final IllegalStateException e) {
|
||||
dialog = null;
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public static CameraDialog newInstance(/* add parameters here if you need */) {
|
||||
final CameraDialog dialog = new CameraDialog();
|
||||
final Bundle args = new Bundle();
|
||||
// add parameters here if you need
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
protected USBMonitor mUSBMonitor;
|
||||
private Spinner mSpinner;
|
||||
private DeviceListAdapter mDeviceListAdapter;
|
||||
|
||||
public CameraDialog(/* no arguments */) {
|
||||
// Fragment need default constructor
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onAttach(final Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if (mUSBMonitor == null)
|
||||
try {
|
||||
mUSBMonitor = ((CameraDialogParent)activity).getUSBMonitor();
|
||||
} catch (final ClassCastException e) {
|
||||
} catch (final NullPointerException e) {
|
||||
}
|
||||
if (mUSBMonitor == null) {
|
||||
throw new ClassCastException(activity.toString() + " must implement CameraDialogParent#getUSBController");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState == null)
|
||||
savedInstanceState = getArguments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(final Bundle saveInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
if (args != null)
|
||||
saveInstanceState.putAll(args);
|
||||
super.onSaveInstanceState(saveInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(initView());
|
||||
builder.setTitle(R.string.select);
|
||||
builder.setPositiveButton(android.R.string.ok, mOnDialogClickListener);
|
||||
builder.setNegativeButton(android.R.string.cancel , mOnDialogClickListener);
|
||||
builder.setNeutralButton(R.string.refresh, null);
|
||||
final Dialog dialog = builder.create();
|
||||
dialog.setCancelable(true);
|
||||
dialog.setCanceledOnTouchOutside(true);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* create view that this fragment shows
|
||||
* @return
|
||||
*/
|
||||
private final View initView() {
|
||||
final View rootView = getActivity().getLayoutInflater().inflate(R.layout.dialog_camera, null);
|
||||
mSpinner = (Spinner)rootView.findViewById(R.id.spinner1);
|
||||
final View empty = rootView.findViewById(android.R.id.empty);
|
||||
mSpinner.setEmptyView(empty);
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateDevices();
|
||||
final Button button = (Button)getDialog().findViewById(android.R.id.button3);
|
||||
if (button != null) {
|
||||
button.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
private final OnClickListener mOnClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
switch (v.getId()) {
|
||||
case android.R.id.button3:
|
||||
updateDevices();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final DialogInterface.OnClickListener mOnDialogClickListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialog, final int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
final Object item = mSpinner.getSelectedItem();
|
||||
if (item instanceof UsbDevice) {
|
||||
mUSBMonitor.requestPermission((UsbDevice)item);
|
||||
((CameraDialogParent)getActivity()).onDialogResult(false);
|
||||
}
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
((CameraDialogParent)getActivity()).onDialogResult(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCancel(final DialogInterface dialog) {
|
||||
((CameraDialogParent)getActivity()).onDialogResult(true);
|
||||
super.onCancel(dialog);
|
||||
}
|
||||
|
||||
public void updateDevices() {
|
||||
// mUSBMonitor.dumpDevices();
|
||||
final List<DeviceFilter> filter = DeviceFilter.getDeviceFilters(getActivity(), R.xml.device_filter);
|
||||
mDeviceListAdapter = new DeviceListAdapter(getActivity(), mUSBMonitor.getDeviceList(filter.get(0)));
|
||||
mSpinner.setAdapter(mDeviceListAdapter);
|
||||
}
|
||||
|
||||
private static final class DeviceListAdapter extends BaseAdapter {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final List<UsbDevice> mList;
|
||||
|
||||
public DeviceListAdapter(final Context context, final List<UsbDevice>list) {
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mList = list != null ? list : new ArrayList<UsbDevice>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getItem(final int position) {
|
||||
if ((position >= 0) && (position < mList.size()))
|
||||
return mList.get(position);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(final int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View convertView, final ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(R.layout.listitem_device, parent, false);
|
||||
}
|
||||
if (convertView instanceof CheckedTextView) {
|
||||
final UsbDevice device = getItem(position);
|
||||
((CheckedTextView)convertView).setText(
|
||||
String.format("UVC Camera:(%x:%x:%s)", device.getVendorId(), device.getProductId(), device.getDeviceName()));
|
||||
}
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class DeviceFilter {
|
||||
|
||||
private static final String TAG = "DeviceFilter";
|
||||
|
||||
// USB Vendor ID (or -1 for unspecified)
|
||||
public final int mVendorId;
|
||||
// USB Product ID (or -1 for unspecified)
|
||||
public final int mProductId;
|
||||
// USB device or interface class (or -1 for unspecified)
|
||||
public final int mClass;
|
||||
// USB device subclass (or -1 for unspecified)
|
||||
public final int mSubclass;
|
||||
// USB device protocol (or -1 for unspecified)
|
||||
public final int mProtocol;
|
||||
// USB device manufacturer name string (or null for unspecified)
|
||||
public final String mManufacturerName;
|
||||
// USB device product name string (or null for unspecified)
|
||||
public final String mProductName;
|
||||
// USB device serial number string (or null for unspecified)
|
||||
public final String mSerialNumber;
|
||||
// set true if specific device(s) should exclude
|
||||
public final boolean isExclude;
|
||||
|
||||
public DeviceFilter(final int vid, final int pid, final int clasz, final int subclass,
|
||||
final int protocol, final String manufacturer, final String product, final String serialNum) {
|
||||
this(vid, pid, clasz, subclass, protocol, manufacturer, product, serialNum, false);
|
||||
}
|
||||
|
||||
public DeviceFilter(final int vid, final int pid, final int clasz, final int subclass,
|
||||
final int protocol, final String manufacturer, final String product, final String serialNum, final boolean isExclude) {
|
||||
mVendorId = vid;
|
||||
mProductId = pid;
|
||||
mClass = clasz;
|
||||
mSubclass = subclass;
|
||||
mProtocol = protocol;
|
||||
mManufacturerName = manufacturer;
|
||||
mProductName = product;
|
||||
mSerialNumber = serialNum;
|
||||
this.isExclude = isExclude;
|
||||
/* Log.i(TAG, String.format("vendorId=0x%04x,productId=0x%04x,class=0x%02x,subclass=0x%02x,protocol=0x%02x",
|
||||
mVendorId, mProductId, mClass, mSubclass, mProtocol)); */
|
||||
}
|
||||
|
||||
public DeviceFilter(final UsbDevice device) {
|
||||
this(device, false);
|
||||
}
|
||||
|
||||
public DeviceFilter(final UsbDevice device, final boolean isExclude) {
|
||||
mVendorId = device.getVendorId();
|
||||
mProductId = device.getProductId();
|
||||
mClass = device.getDeviceClass();
|
||||
mSubclass = device.getDeviceSubclass();
|
||||
mProtocol = device.getDeviceProtocol();
|
||||
mManufacturerName = null; // device.getManufacturerName();
|
||||
mProductName = null; // device.getProductName();
|
||||
mSerialNumber = null; // device.getSerialNumber();
|
||||
this.isExclude = isExclude;
|
||||
/* Log.i(TAG, String.format("vendorId=0x%04x,productId=0x%04x,class=0x%02x,subclass=0x%02x,protocol=0x%02x",
|
||||
mVendorId, mProductId, mClass, mSubclass, mProtocol)); */
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したxmlリソースからDeviceFilterリストを生成する
|
||||
* @param context
|
||||
* @param deviceFilterXmlId
|
||||
* @return
|
||||
*/
|
||||
public static List<DeviceFilter> getDeviceFilters(final Context context, final int deviceFilterXmlId) {
|
||||
final XmlPullParser parser = context.getResources().getXml(deviceFilterXmlId);
|
||||
final List<DeviceFilter> deviceFilters = new ArrayList<DeviceFilter>();
|
||||
try {
|
||||
int eventType = parser.getEventType();
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
final DeviceFilter deviceFilter = readEntryOne(context, parser);
|
||||
if (deviceFilter != null) {
|
||||
deviceFilters.add(deviceFilter);
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
} catch (final XmlPullParserException e) {
|
||||
Log.d(TAG, "XmlPullParserException", e);
|
||||
} catch (final IOException e) {
|
||||
Log.d(TAG, "IOException", e);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(deviceFilters);
|
||||
}
|
||||
|
||||
/**
|
||||
* read as integer values with default value from xml(w/o exception throws)
|
||||
* resource integer id is also resolved into integer
|
||||
* @param parser
|
||||
* @param namespace
|
||||
* @param name
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
private static final int getAttributeInteger(final Context context, final XmlPullParser parser, final String namespace, final String name, final int defaultValue) {
|
||||
int result = defaultValue;
|
||||
try {
|
||||
String v = parser.getAttributeValue(namespace, name);
|
||||
if (!TextUtils.isEmpty(v) && v.startsWith("@")) {
|
||||
final String r = v.substring(1);
|
||||
final int resId = context.getResources().getIdentifier(r, null, context.getPackageName());
|
||||
if (resId > 0) {
|
||||
result = context.getResources().getInteger(resId);
|
||||
}
|
||||
} else {
|
||||
int radix = 10;
|
||||
if (v != null && v.length() > 2 && v.charAt(0) == '0' &&
|
||||
(v.charAt(1) == 'x' || v.charAt(1) == 'X')) {
|
||||
// allow hex values starting with 0x or 0X
|
||||
radix = 16;
|
||||
v = v.substring(2);
|
||||
}
|
||||
result = Integer.parseInt(v, radix);
|
||||
}
|
||||
} catch (final NotFoundException e) {
|
||||
result = defaultValue;
|
||||
} catch (final NumberFormatException e) {
|
||||
result = defaultValue;
|
||||
} catch (final NullPointerException e) {
|
||||
result = defaultValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* read as boolean values with default value from xml(w/o exception throws)
|
||||
* resource boolean id is also resolved into boolean
|
||||
* if the value is zero, return false, if the value is non-zero integer, return true
|
||||
* @param context
|
||||
* @param parser
|
||||
* @param namespace
|
||||
* @param name
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
private static final boolean getAttributeBoolean(final Context context, final XmlPullParser parser, final String namespace, final String name, final boolean defaultValue) {
|
||||
boolean result = defaultValue;
|
||||
try {
|
||||
String v = parser.getAttributeValue(namespace, name);
|
||||
if ("TRUE".equalsIgnoreCase(v)) {
|
||||
result = true;
|
||||
} else if ("FALSE".equalsIgnoreCase(v)) {
|
||||
result = false;
|
||||
} else if (!TextUtils.isEmpty(v) && v.startsWith("@")) {
|
||||
final String r = v.substring(1);
|
||||
final int resId = context.getResources().getIdentifier(r, null, context.getPackageName());
|
||||
if (resId > 0) {
|
||||
result = context.getResources().getBoolean(resId);
|
||||
}
|
||||
} else {
|
||||
int radix = 10;
|
||||
if (v != null && v.length() > 2 && v.charAt(0) == '0' &&
|
||||
(v.charAt(1) == 'x' || v.charAt(1) == 'X')) {
|
||||
// allow hex values starting with 0x or 0X
|
||||
radix = 16;
|
||||
v = v.substring(2);
|
||||
}
|
||||
final int val = Integer.parseInt(v, radix);
|
||||
result = val != 0;
|
||||
}
|
||||
} catch (final NotFoundException e) {
|
||||
result = defaultValue;
|
||||
} catch (final NumberFormatException e) {
|
||||
result = defaultValue;
|
||||
} catch (final NullPointerException e) {
|
||||
result = defaultValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* read as String attribute with default value from xml(w/o exception throws)
|
||||
* resource string id is also resolved into string
|
||||
* @param parser
|
||||
* @param namespace
|
||||
* @param name
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
private static final String getAttributeString(final Context context, final XmlPullParser parser, final String namespace, final String name, final String defaultValue) {
|
||||
String result = defaultValue;
|
||||
try {
|
||||
result = parser.getAttributeValue(namespace, name);
|
||||
if (result == null)
|
||||
result = defaultValue;
|
||||
if (!TextUtils.isEmpty(result) && result.startsWith("@")) {
|
||||
final String r = result.substring(1);
|
||||
final int resId = context.getResources().getIdentifier(r, null, context.getPackageName());
|
||||
if (resId > 0)
|
||||
result = context.getResources().getString(resId);
|
||||
}
|
||||
} catch (final NotFoundException e) {
|
||||
result = defaultValue;
|
||||
} catch (final NumberFormatException e) {
|
||||
result = defaultValue;
|
||||
} catch (final NullPointerException e) {
|
||||
result = defaultValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static DeviceFilter readEntryOne(final Context context, final XmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
int vendorId = -1;
|
||||
int productId = -1;
|
||||
int deviceClass = -1;
|
||||
int deviceSubclass = -1;
|
||||
int deviceProtocol = -1;
|
||||
boolean exclude = false;
|
||||
String manufacturerName = null;
|
||||
String productName = null;
|
||||
String serialNumber = null;
|
||||
boolean hasValue = false;
|
||||
|
||||
String tag;
|
||||
int eventType = parser.getEventType();
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
tag = parser.getName();
|
||||
if (!TextUtils.isEmpty(tag) && (tag.equalsIgnoreCase("usb-device"))) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
hasValue = true;
|
||||
vendorId = getAttributeInteger(context, parser, null, "vendor-id", -1);
|
||||
if (vendorId == -1) {
|
||||
vendorId = getAttributeInteger(context, parser, null, "vendorId", -1);
|
||||
if (vendorId == -1)
|
||||
vendorId = getAttributeInteger(context, parser, null, "venderId", -1);
|
||||
}
|
||||
productId = getAttributeInteger(context, parser, null, "product-id", -1);
|
||||
if (productId == -1)
|
||||
productId = getAttributeInteger(context, parser, null, "productId", -1);
|
||||
deviceClass = getAttributeInteger(context, parser, null, "class", -1);
|
||||
deviceSubclass = getAttributeInteger(context, parser, null, "subclass", -1);
|
||||
deviceProtocol = getAttributeInteger(context, parser, null, "protocol", -1);
|
||||
manufacturerName = getAttributeString(context, parser, null, "manufacturer-name", null);
|
||||
if (TextUtils.isEmpty(manufacturerName))
|
||||
manufacturerName = getAttributeString(context, parser, null, "manufacture", null);
|
||||
productName = getAttributeString(context, parser, null, "product-name", null);
|
||||
if (TextUtils.isEmpty(productName))
|
||||
productName = getAttributeString(context, parser, null, "product", null);
|
||||
serialNumber = getAttributeString(context, parser, null, "serial-number", null);
|
||||
if (TextUtils.isEmpty(serialNumber))
|
||||
serialNumber = getAttributeString(context, parser, null, "serial", null);
|
||||
exclude = getAttributeBoolean(context, parser, null, "exclude", false);
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (hasValue) {
|
||||
return new DeviceFilter(vendorId, productId, deviceClass,
|
||||
deviceSubclass, deviceProtocol, manufacturerName, productName,
|
||||
serialNumber, exclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* public void write(XmlSerializer serializer) throws IOException {
|
||||
serializer.startTag(null, "usb-device");
|
||||
if (mVendorId != -1) {
|
||||
serializer
|
||||
.attribute(null, "vendor-id", Integer.toString(mVendorId));
|
||||
}
|
||||
if (mProductId != -1) {
|
||||
serializer.attribute(null, "product-id",
|
||||
Integer.toString(mProductId));
|
||||
}
|
||||
if (mClass != -1) {
|
||||
serializer.attribute(null, "class", Integer.toString(mClass));
|
||||
}
|
||||
if (mSubclass != -1) {
|
||||
serializer.attribute(null, "subclass", Integer.toString(mSubclass));
|
||||
}
|
||||
if (mProtocol != -1) {
|
||||
serializer.attribute(null, "protocol", Integer.toString(mProtocol));
|
||||
}
|
||||
if (mManufacturerName != null) {
|
||||
serializer.attribute(null, "manufacturer-name", mManufacturerName);
|
||||
}
|
||||
if (mProductName != null) {
|
||||
serializer.attribute(null, "product-name", mProductName);
|
||||
}
|
||||
if (mSerialNumber != null) {
|
||||
serializer.attribute(null, "serial-number", mSerialNumber);
|
||||
}
|
||||
serializer.attribute(null, "serial-number", Boolean.toString(isExclude));
|
||||
serializer.endTag(null, "usb-device");
|
||||
} */
|
||||
|
||||
/**
|
||||
* 指定したクラス・サブクラス・プロトコルがこのDeviceFilterとマッチするかどうかを返す
|
||||
* mExcludeフラグは別途#isExcludeか自前でチェックすること
|
||||
* @param clasz
|
||||
* @param subclass
|
||||
* @param protocol
|
||||
* @return
|
||||
*/
|
||||
private boolean matches(final int clasz, final int subclass, final int protocol) {
|
||||
return ((mClass == -1 || clasz == mClass)
|
||||
&& (mSubclass == -1 || subclass == mSubclass) && (mProtocol == -1 || protocol == mProtocol));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したUsbDeviceがこのDeviceFilterにマッチするかどうかを返す
|
||||
* mExcludeフラグは別途#isExcludeか自前でチェックすること
|
||||
* @param device
|
||||
* @return
|
||||
*/
|
||||
public boolean matches(final UsbDevice device) {
|
||||
if (mVendorId != -1 && device.getVendorId() != mVendorId) {
|
||||
return false;
|
||||
}
|
||||
if (mProductId != -1 && device.getProductId() != mProductId) {
|
||||
return false;
|
||||
}
|
||||
/* if (mManufacturerName != null && device.getManufacturerName() == null)
|
||||
return false;
|
||||
if (mProductName != null && device.getProductName() == null)
|
||||
return false;
|
||||
if (mSerialNumber != null && device.getSerialNumber() == null)
|
||||
return false;
|
||||
if (mManufacturerName != null && device.getManufacturerName() != null
|
||||
&& !mManufacturerName.equals(device.getManufacturerName()))
|
||||
return false;
|
||||
if (mProductName != null && device.getProductName() != null
|
||||
&& !mProductName.equals(device.getProductName()))
|
||||
return false;
|
||||
if (mSerialNumber != null && device.getSerialNumber() != null
|
||||
&& !mSerialNumber.equals(device.getSerialNumber()))
|
||||
return false; */
|
||||
|
||||
// check device class/subclass/protocol
|
||||
if (matches(device.getDeviceClass(), device.getDeviceSubclass(), device.getDeviceProtocol())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if device doesn't match, check the interfaces
|
||||
final int count = device.getInterfaceCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final UsbInterface intf = device.getInterface(i);
|
||||
if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), intf.getInterfaceProtocol())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* このDeviceFilterに一致してかつmExcludeがtrueならtrueを返す
|
||||
* @param device
|
||||
* @return
|
||||
*/
|
||||
public boolean isExclude(final UsbDevice device) {
|
||||
return isExclude && matches(device);
|
||||
}
|
||||
|
||||
/**
|
||||
* これって要らんかも, equalsでできる気が
|
||||
* @param f
|
||||
* @return
|
||||
*/
|
||||
public boolean matches(final DeviceFilter f) {
|
||||
if (isExclude != f.isExclude) {
|
||||
return false;
|
||||
}
|
||||
if (mVendorId != -1 && f.mVendorId != mVendorId) {
|
||||
return false;
|
||||
}
|
||||
if (mProductId != -1 && f.mProductId != mProductId) {
|
||||
return false;
|
||||
}
|
||||
if (f.mManufacturerName != null && mManufacturerName == null) {
|
||||
return false;
|
||||
}
|
||||
if (f.mProductName != null && mProductName == null) {
|
||||
return false;
|
||||
}
|
||||
if (f.mSerialNumber != null && mSerialNumber == null) {
|
||||
return false;
|
||||
}
|
||||
if (mManufacturerName != null && f.mManufacturerName != null
|
||||
&& !mManufacturerName.equals(f.mManufacturerName)) {
|
||||
return false;
|
||||
}
|
||||
if (mProductName != null && f.mProductName != null
|
||||
&& !mProductName.equals(f.mProductName)) {
|
||||
return false;
|
||||
}
|
||||
if (mSerialNumber != null && f.mSerialNumber != null
|
||||
&& !mSerialNumber.equals(f.mSerialNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check device class/subclass/protocol
|
||||
return matches(f.mClass, f.mSubclass, f.mProtocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
// can't compare if we have wildcard strings
|
||||
if (mVendorId == -1 || mProductId == -1 || mClass == -1
|
||||
|| mSubclass == -1 || mProtocol == -1) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof DeviceFilter) {
|
||||
final DeviceFilter filter = (DeviceFilter) obj;
|
||||
|
||||
if (filter.mVendorId != mVendorId
|
||||
|| filter.mProductId != mProductId
|
||||
|| filter.mClass != mClass || filter.mSubclass != mSubclass
|
||||
|| filter.mProtocol != mProtocol) {
|
||||
return false;
|
||||
}
|
||||
if ((filter.mManufacturerName != null && mManufacturerName == null)
|
||||
|| (filter.mManufacturerName == null && mManufacturerName != null)
|
||||
|| (filter.mProductName != null && mProductName == null)
|
||||
|| (filter.mProductName == null && mProductName != null)
|
||||
|| (filter.mSerialNumber != null && mSerialNumber == null)
|
||||
|| (filter.mSerialNumber == null && mSerialNumber != null)) {
|
||||
return false;
|
||||
}
|
||||
if ((filter.mManufacturerName != null && mManufacturerName != null && !mManufacturerName
|
||||
.equals(filter.mManufacturerName))
|
||||
|| (filter.mProductName != null && mProductName != null && !mProductName
|
||||
.equals(filter.mProductName))
|
||||
|| (filter.mSerialNumber != null && mSerialNumber != null && !mSerialNumber
|
||||
.equals(filter.mSerialNumber))) {
|
||||
return false;
|
||||
}
|
||||
return (filter.isExclude != isExclude);
|
||||
}
|
||||
if (obj instanceof UsbDevice) {
|
||||
final UsbDevice device = (UsbDevice) obj;
|
||||
if (isExclude
|
||||
|| (device.getVendorId() != mVendorId)
|
||||
|| (device.getProductId() != mProductId)
|
||||
|| (device.getDeviceClass() != mClass)
|
||||
|| (device.getDeviceSubclass() != mSubclass)
|
||||
|| (device.getDeviceProtocol() != mProtocol) ) {
|
||||
return false;
|
||||
}
|
||||
/* if ((mManufacturerName != null && device.getManufacturerName() == null)
|
||||
|| (mManufacturerName == null && device
|
||||
.getManufacturerName() != null)
|
||||
|| (mProductName != null && device.getProductName() == null)
|
||||
|| (mProductName == null && device.getProductName() != null)
|
||||
|| (mSerialNumber != null && device.getSerialNumber() == null)
|
||||
|| (mSerialNumber == null && device.getSerialNumber() != null)) {
|
||||
return (false);
|
||||
} */
|
||||
/* if ((device.getManufacturerName() != null && !mManufacturerName
|
||||
.equals(device.getManufacturerName()))
|
||||
|| (device.getProductName() != null && !mProductName
|
||||
.equals(device.getProductName()))
|
||||
|| (device.getSerialNumber() != null && !mSerialNumber
|
||||
.equals(device.getSerialNumber()))) {
|
||||
return (false);
|
||||
} */
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (((mVendorId << 16) | mProductId) ^ ((mClass << 16)
|
||||
| (mSubclass << 8) | mProtocol));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId="
|
||||
+ mProductId + ",mClass=" + mClass + ",mSubclass=" + mSubclass
|
||||
+ ",mProtocol=" + mProtocol
|
||||
+ ",mManufacturerName=" + mManufacturerName
|
||||
+ ",mProductName=" + mProductName
|
||||
+ ",mSerialNumber=" + mSerialNumber
|
||||
+ ",isExclude=" + isExclude
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.serenegiant.usb;
|
||||
|
||||
public interface IButtonCallback {
|
||||
void onButton(int button, int state);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Callback interface for UVCCamera class
|
||||
* If you need frame data as ByteBuffer, you can use this callback interface with UVCCamera#setFrameCallback
|
||||
*/
|
||||
public interface IFrameCallback {
|
||||
/**
|
||||
* This method is called from native library via JNI on the same thread as UVCCamera#startCapture.
|
||||
* You can use both UVCCamera#startCapture and #setFrameCallback
|
||||
* but it is better to use either for better performance.
|
||||
* You can also pass pixel format type to UVCCamera#setFrameCallback for this method.
|
||||
* Some frames may drops if this method takes a time.
|
||||
* When you use some color format like NV21, this library never execute color space conversion,
|
||||
* just execute pixel format conversion. If you want to get same result as on screen, please try to
|
||||
* consider to get images via texture(SurfaceTexture) and read pixel buffer from it using OpenGL|ES2/3
|
||||
* instead of using IFrameCallback(this way is much efficient in most case than using IFrameCallback).
|
||||
* @param frame this is direct ByteBuffer from JNI layer and you should handle it's byte order and limitation.
|
||||
*/
|
||||
public void onFrame(ByteBuffer frame);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.serenegiant.usb;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface IStatusCallback {
|
||||
void onStatus(int statusClass, int event, int selector, int statusAttribute, ByteBuffer data);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class Size implements Parcelable {
|
||||
//
|
||||
/**
|
||||
* native側のuvc_raw_format_tの値, こっちは主にlibuvc用
|
||||
* 9999 is still image
|
||||
*/
|
||||
public int type;
|
||||
/**
|
||||
* native側のraw_frame_tの値, androusb用,
|
||||
* libuvcは対応していない
|
||||
*/
|
||||
public int frame_type;
|
||||
public int index;
|
||||
public int width;
|
||||
public int height;
|
||||
public int frameIntervalType;
|
||||
public int frameIntervalIndex;
|
||||
public int[] intervals;
|
||||
// ここ以下はframeIntervalTypeとintervalsから#updateFrameRateで計算する
|
||||
public float[] fps;
|
||||
private String frameRates;
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
* @param _type native側のraw_format_tの値, ただし9999は静止画
|
||||
* @param _frame_type native側のraw_frame_tの値
|
||||
* @param _index
|
||||
* @param _width
|
||||
* @param _height
|
||||
*/
|
||||
public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height) {
|
||||
type = _type;
|
||||
frame_type = _frame_type;
|
||||
index = _index;
|
||||
width = _width;
|
||||
height = _height;
|
||||
frameIntervalType = -1;
|
||||
frameIntervalIndex = 0;
|
||||
intervals = null;
|
||||
updateFrameRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
* @param _type native側のraw_format_tの値, ただし9999は静止画
|
||||
* @param _frame_type native側のraw_frame_tの値
|
||||
* @param _index
|
||||
* @param _width
|
||||
* @param _height
|
||||
* @param _min_intervals
|
||||
* @param _max_intervals
|
||||
*/
|
||||
public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int _min_intervals, final int _max_intervals, final int _step) {
|
||||
type = _type;
|
||||
frame_type = _frame_type;
|
||||
index = _index;
|
||||
width = _width;
|
||||
height = _height;
|
||||
frameIntervalType = 0;
|
||||
frameIntervalIndex = 0;
|
||||
intervals = new int[3];
|
||||
intervals[0] = _min_intervals;
|
||||
intervals[1] = _max_intervals;
|
||||
intervals[2] = _step;
|
||||
updateFrameRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
* @param _type native側のraw_format_tの値, ただし9999は静止画
|
||||
* @param _frame_type native側のraw_frame_tの値
|
||||
* @param _index
|
||||
* @param _width
|
||||
* @param _height
|
||||
* @param _intervals
|
||||
*/
|
||||
public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int[] _intervals) {
|
||||
type = _type;
|
||||
frame_type = _frame_type;
|
||||
index = _index;
|
||||
width = _width;
|
||||
height = _height;
|
||||
final int n = _intervals != null ? _intervals.length : -1;
|
||||
if (n > 0) {
|
||||
frameIntervalType = n;
|
||||
intervals = new int[n];
|
||||
System.arraycopy(_intervals, 0, intervals, 0, n);
|
||||
} else {
|
||||
frameIntervalType = -1;
|
||||
intervals = null;
|
||||
}
|
||||
frameIntervalIndex = 0;
|
||||
updateFrameRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* コピーコンストラクタ
|
||||
* @param other
|
||||
*/
|
||||
public Size(final Size other) {
|
||||
type = other.type;
|
||||
frame_type = other.frame_type;
|
||||
index = other.index;
|
||||
width = other.width;
|
||||
height = other.height;
|
||||
frameIntervalType = other.frameIntervalType;
|
||||
frameIntervalIndex = other.frameIntervalIndex;
|
||||
final int n = other.intervals != null ? other.intervals.length : -1;
|
||||
if (n > 0) {
|
||||
intervals = new int[n];
|
||||
System.arraycopy(other.intervals, 0, intervals, 0, n);
|
||||
} else {
|
||||
intervals = null;
|
||||
}
|
||||
updateFrameRate();
|
||||
}
|
||||
|
||||
private Size(final Parcel source) {
|
||||
// 読み取り順はwriteToParcelでの書き込み順と同じでないとダメ
|
||||
type = source.readInt();
|
||||
frame_type = source.readInt();
|
||||
index = source.readInt();
|
||||
width = source.readInt();
|
||||
height = source.readInt();
|
||||
frameIntervalType = source.readInt();
|
||||
frameIntervalIndex = source.readInt();
|
||||
if (frameIntervalType >= 0) {
|
||||
if (frameIntervalType > 0) {
|
||||
intervals = new int[frameIntervalType];
|
||||
} else {
|
||||
intervals = new int[3];
|
||||
}
|
||||
source.readIntArray(intervals);
|
||||
} else {
|
||||
intervals = null;
|
||||
}
|
||||
updateFrameRate();
|
||||
}
|
||||
|
||||
public Size set(final Size other) {
|
||||
if (other != null) {
|
||||
type = other.type;
|
||||
frame_type = other.frame_type;
|
||||
index = other.index;
|
||||
width = other.width;
|
||||
height = other.height;
|
||||
frameIntervalType = other.frameIntervalType;
|
||||
frameIntervalIndex = other.frameIntervalIndex;
|
||||
final int n = other.intervals != null ? other.intervals.length : -1;
|
||||
if (n > 0) {
|
||||
intervals = new int[n];
|
||||
System.arraycopy(other.intervals, 0, intervals, 0, n);
|
||||
} else {
|
||||
intervals = null;
|
||||
}
|
||||
updateFrameRate();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getCurrentFrameRate() throws IllegalStateException {
|
||||
final int n = fps != null ? fps.length : 0;
|
||||
if ((frameIntervalIndex >= 0) && (frameIntervalIndex < n)) {
|
||||
return fps[frameIntervalIndex];
|
||||
}
|
||||
throw new IllegalStateException("unknown frame rate or not ready");
|
||||
}
|
||||
|
||||
public void setCurrentFrameRate(final float frameRate) {
|
||||
// 一番近いのを選ぶ
|
||||
int index = -1;
|
||||
final int n = fps != null ? fps.length : 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (fps[i] <= frameRate) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
frameIntervalIndex = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeInt(type);
|
||||
dest.writeInt(frame_type);
|
||||
dest.writeInt(index);
|
||||
dest.writeInt(width);
|
||||
dest.writeInt(height);
|
||||
dest.writeInt(frameIntervalType);
|
||||
dest.writeInt(frameIntervalIndex);
|
||||
if (intervals != null) {
|
||||
dest.writeIntArray(intervals);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFrameRate() {
|
||||
final int n = frameIntervalType;
|
||||
if (n > 0) {
|
||||
fps = new float[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
final float _fps = fps[i] = 10000000.0f / intervals[i];
|
||||
}
|
||||
} else if (n == 0) {
|
||||
try {
|
||||
final int min = Math.min(intervals[0], intervals[1]);
|
||||
final int max = Math.max(intervals[0], intervals[1]);
|
||||
final int step = intervals[2];
|
||||
if (step > 0) {
|
||||
int m = 0;
|
||||
for (int i = min; i <= max; i+= step) { m++; }
|
||||
fps = new float[m];
|
||||
m = 0;
|
||||
for (int i = min; i <= max; i+= step) {
|
||||
final float _fps = fps[m++] = 10000000.0f / i;
|
||||
}
|
||||
} else {
|
||||
final float max_fps = 10000000.0f / min;
|
||||
int m = 0;
|
||||
for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { m++; }
|
||||
fps = new float[m];
|
||||
m = 0;
|
||||
for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) {
|
||||
this.fps[m++] = fps;
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore, なんでかminとmaxが0になってるんちゃうかな
|
||||
fps = null;
|
||||
}
|
||||
}
|
||||
final int m = fps != null ? fps.length : 0;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
for (int i = 0; i < m; i++) {
|
||||
sb.append(String.format(Locale.US, "%4.1f", fps[i]));
|
||||
if (i < m-1) {
|
||||
sb.append(",");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
frameRates = sb.toString();
|
||||
if (frameIntervalIndex > m) {
|
||||
frameIntervalIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
float frame_rate = 0.0f;
|
||||
try {
|
||||
frame_rate = getCurrentFrameRate();
|
||||
} catch (final Exception e) {
|
||||
}
|
||||
return String.format(Locale.US, "Size(%dx%d@%4.1f,type:%d,frame:%d,index:%d,%s)", width, height, frame_rate, type, frame_type, index, frameRates);
|
||||
}
|
||||
|
||||
public static final Creator<Size> CREATOR = new Creator<Size>() {
|
||||
@Override
|
||||
public Size createFromParcel(final Parcel source) {
|
||||
return new Size(source);
|
||||
}
|
||||
@Override
|
||||
public Size[] newArray(final int size) {
|
||||
return new Size[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,859 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class USBVendorId {
|
||||
private static final SparseArray<String> IDS = new SparseArray<String>();
|
||||
|
||||
public static String vendorName(final int vendor_id) {
|
||||
return IDS.get(vendor_id);
|
||||
}
|
||||
|
||||
static {
|
||||
IDS.put(10006, "YUEN DA ELECTRONIC PRODUCTS FACTORY");
|
||||
IDS.put(10013, "Gionee Communication Equipment Co., Ltd. ShenZhen");
|
||||
IDS.put(10022, "Universal Electronics Inc. (dba: TVIEW)");
|
||||
IDS.put(1003, "Atmel Corporation");
|
||||
IDS.put(1006, "Mitsumi");
|
||||
IDS.put(1008, "HP Inc.");
|
||||
IDS.put(10112, "M31 Technology Corp.");
|
||||
IDS.put(10113, "Liteconn Co., Ltd.");
|
||||
IDS.put(10121, "Suzhou WEIJU Electronics Technology Co., Ltd.");
|
||||
IDS.put(10144, "Mondokey Limited");
|
||||
IDS.put(10149, "Advantest Corporation");
|
||||
IDS.put(10150, "iRobot Corporation");
|
||||
IDS.put(1020, "Elitegroup Computer Systems");
|
||||
IDS.put(1021, "Xilinx Inc.");
|
||||
IDS.put(10226, "Sibridge Tech.");
|
||||
IDS.put(1026, "ALi Corporation");
|
||||
IDS.put(1027, "Future Technology Devices International Limited");
|
||||
IDS.put(10275, "Dongguan Jiumutong Industry Co., Ltd.");
|
||||
IDS.put(10289, "Power Integrations");
|
||||
IDS.put(10291, "Oculus VR, Inc.");
|
||||
IDS.put(10300, "HIGH TEK HARNESS ENTERPRISE CO., LTD.");
|
||||
IDS.put(10316, "Full in Hope Co., Ltd.");
|
||||
IDS.put(1032, "Quanta Computer Inc.");
|
||||
IDS.put(10329, "Viconn Technology (HK) Co., Ltd.");
|
||||
IDS.put(1033, "NEC Corporation");
|
||||
IDS.put(1035, "Weltrend Semiconductor");
|
||||
IDS.put(1037, "VIA Technologies, Inc.");
|
||||
IDS.put(10374, "Seeed Technology Co., Ltd.");
|
||||
IDS.put(10375, "Specwerkz");
|
||||
IDS.put(1038, "MCCI Corporation");
|
||||
IDS.put(10398, "Esselte Leitz GmbH & Co. KG");
|
||||
IDS.put(10406, "E-SEEK Inc.");
|
||||
IDS.put(1041, "BUFFALO INC.");
|
||||
IDS.put(10423, "Pleora Technologies Inc.");
|
||||
IDS.put(10431, "Vitetech Int'l Co., Ltd.");
|
||||
IDS.put(1044, "Giga-Byte Technology Co., Ltd.");
|
||||
IDS.put(10446, "Changzhou Shi Wujin Miqi East Electronic Co., Ltd.");
|
||||
IDS.put(10457, "Shenzhen Ourconn Technology Co., Ltd.");
|
||||
IDS.put(10458, "G.SKILL Int'l Enterprice Co., Ltd.");
|
||||
IDS.put(1046, "Nuvoton Technology Corp.");
|
||||
IDS.put(10466, "Surplus Electronic Technology Co., Ltd.");
|
||||
IDS.put(10470, "BIAMP SYSTEMS");
|
||||
IDS.put(10509, "IBCONN Technologies (Shenzhen) Co., Ltd.");
|
||||
IDS.put(10510, "Fugoo Inc.");
|
||||
IDS.put(10519, "Pan Xin Precision Electronics Co., Ltd.");
|
||||
IDS.put(10530, "Dongguan Digi-in Digital Technology Co., Ltd.");
|
||||
IDS.put(1054, "Creative Labs");
|
||||
IDS.put(10540, "GENUSION, Inc.");
|
||||
IDS.put(10544, "Ineda Systems Inc.");
|
||||
IDS.put(10545, "Jolla Ltd.");
|
||||
IDS.put(10546, "Peraso Technologies, Inc.");
|
||||
IDS.put(10549, "Nanjing Magewell Electronics Co., Ltd.");
|
||||
IDS.put(10560, "Shenzhen Yiwanda Electronics Co., Ltd.");
|
||||
IDS.put(1057, "Nokia Corporation");
|
||||
IDS.put(10575, "Dollar Connection Ltd.");
|
||||
IDS.put(10595, "BIO-key International, Inc.");
|
||||
IDS.put(1060, "Microchip-SMSC");
|
||||
IDS.put(10603, "Xacti Corporation");
|
||||
IDS.put(10615, "Shenzhen Zowee Technology Co., Ltd.");
|
||||
IDS.put(10643, "ADPlaus Technology Limited");
|
||||
IDS.put(10646, "Unwired Technology");
|
||||
IDS.put(1065, "Cirrus Logic Inc.");
|
||||
IDS.put(10657, "Union Electric Plug & Connector Corp.");
|
||||
IDS.put(10674, "Canova Tech");
|
||||
IDS.put(10685, "Silicon Works");
|
||||
IDS.put(10695, "HANRICO ANFU ELECTRONICS CO., LTD.");
|
||||
IDS.put(10700, "Kodak Alaris");
|
||||
IDS.put(10702, "JGR Optics Inc.");
|
||||
IDS.put(10703, "Richtek Technology Corporation");
|
||||
IDS.put(10705, "Binatone Electronics Int. Ltd.");
|
||||
IDS.put(1071, "Molex Inc.");
|
||||
IDS.put(10715, "Shenzhen iBoard Technology Co., Ltd.");
|
||||
IDS.put(10719, "SMIT(HK) Limited");
|
||||
IDS.put(1072, "Fujitsu Component Limited");
|
||||
IDS.put(10725, "Dongguan Kechenda Electronic Technology Co., Ltd.");
|
||||
IDS.put(10726, "Fengshun Peiying Electro-Acoustic Co., Ltd.");
|
||||
IDS.put(10744, "MD ELEKTRONIK GmbH");
|
||||
IDS.put(10749, "Bad Elf, LLC");
|
||||
IDS.put(10770, "Vreo Limited");
|
||||
IDS.put(10772, "Kanex");
|
||||
IDS.put(10781, "Oxford Nanopore Technologies");
|
||||
IDS.put(10782, "Obsidian Technology");
|
||||
IDS.put(10783, "Lucent Trans Electronics Co., Ltd.");
|
||||
IDS.put(10784, "GUOGUANG GROUP CO., LTD.");
|
||||
IDS.put(10788, "CNPLUS");
|
||||
IDS.put(10789, "Fourstar Group");
|
||||
IDS.put(10790, "Tragant International Co., Ltd.");
|
||||
IDS.put(10791, "DongGuan LianGang Optoelectronic Technology Co., Ltd.");
|
||||
IDS.put(10797, "Atrust Computer Corp.");
|
||||
IDS.put(10798, "VIA Alliance Semiconductor Co., Ltd.");
|
||||
IDS.put(10799, "BSUN Electronics Co., Ltd.");
|
||||
IDS.put(1080, "Advanced Micro Devices");
|
||||
IDS.put(10807, "RTD Embedded Technologies, Inc.");
|
||||
IDS.put(10816, "Shenzhen Choseal Industrial Co., Ltd.");
|
||||
IDS.put(10817, "Canyon Semiconductor");
|
||||
IDS.put(10818, "Spectra7 Microsystems Corp.");
|
||||
IDS.put(10821, "Meizu Technology Co., Ltd.");
|
||||
IDS.put(10822, "Hubei Yingtong Telecommunication Cable Inc.");
|
||||
IDS.put(10829, "Wilder Technologies");
|
||||
IDS.put(10837, "Diodes Inc.");
|
||||
IDS.put(10846, "DuPont");
|
||||
IDS.put(1085, "Lexmark International Inc.");
|
||||
IDS.put(10852, "Zhejiang Songcheng Electronics Co., Ltd.");
|
||||
IDS.put(10859, "VSN Mobil");
|
||||
IDS.put(10875, "Bellwether Electronic Corp.");
|
||||
IDS.put(10878, "VAIO Corporation");
|
||||
IDS.put(10879, "Perixx Computer GmbH");
|
||||
IDS.put(10885, "HANK ELECTRONICS CO., LTD");
|
||||
IDS.put(10892, "Sonnet Technologies, Inc.");
|
||||
IDS.put(10893, "Keysight Technologies Inc.");
|
||||
IDS.put(10895, "Manutronics Vietnam Joint Stock Company");
|
||||
IDS.put(10900, "G2 Touch Co., Ltd.");
|
||||
IDS.put(10902, "Micromax Informatics Ltd");
|
||||
IDS.put(10910, "SEIKO SOLUTIONS Inc.");
|
||||
IDS.put(10912, "Casco Products Corp.");
|
||||
IDS.put(10922, "Virtium Technology, Inc.");
|
||||
IDS.put(10923, "Field and Company LLC, dba Leef USA");
|
||||
IDS.put(10928, "GM Global Technology Operations LLC");
|
||||
IDS.put(10931, "Key Asic Inc.");
|
||||
IDS.put(10943, "Revolabs, Inc.");
|
||||
IDS.put(10945, "Lattice Semiconductor Corp");
|
||||
IDS.put(10947, "Foshan Nanhai Saga Audio Equipment Co., Ltd.");
|
||||
IDS.put(10957, "Silergy Corp.");
|
||||
IDS.put(10963, "Shenzhen Hali-Power Industrial Co., Ltd.");
|
||||
IDS.put(10971, "I-PEX (Dai-ichi Seiko)");
|
||||
IDS.put(10973, "SEE-PLUS INDUSTRIAL LTD.");
|
||||
IDS.put(10990, "Adapt-IP Company");
|
||||
IDS.put(10997, "Libratone A/S");
|
||||
IDS.put(10999, "Shenzhen Hazens Automotive Electronics (SZ) Co., Ltd.");
|
||||
IDS.put(11000, "Jiangsu Toppower Automotive Electronics Co., Ltd.");
|
||||
IDS.put(11001, "Drapho Electronics Technology Co., Ltd.");
|
||||
IDS.put(1102, "Alps Electric Co., Ltd.");
|
||||
IDS.put(11022, "Le Shi Zhi Xin Electronic Technology (Tian Jin) Limited");
|
||||
IDS.put(11024, "Cardiac Insight, Inc.");
|
||||
IDS.put(11028, "EverPro Technologies Company, Ltd.");
|
||||
IDS.put(11029, "Rosenberger Hochfrequenztechnik");
|
||||
IDS.put(11035, "Dongguan City Sanji Electronics Co., Ltd.");
|
||||
IDS.put(11037, "Lintes Technology Co., Ltd.");
|
||||
IDS.put(11039, "KinnexA, Inc.");
|
||||
IDS.put(11042, "Metra Electronics Corp.");
|
||||
IDS.put(11044, "KeepKey, LLC");
|
||||
IDS.put(11047, "FluxData Incorporated");
|
||||
IDS.put(1105, "Texas Instruments");
|
||||
IDS.put(11061, "Assem Technology Co., Ltd.");
|
||||
IDS.put(11062, "Dongguan City Jianghan Electronics Co., Ltd.");
|
||||
IDS.put(11063, "Huizhou Desay SV Automotive Co., Ltd.");
|
||||
IDS.put(11064, "Ningbo Rixing Electronics Co., Ltd.");
|
||||
IDS.put(11069, "GuangDong YuanFeng Automotive Electroics Co., Ltd.");
|
||||
IDS.put(11080, "Sounding Audio Industrial Limited");
|
||||
IDS.put(11082, "Yueqing Huaxin Electronic Co., Ltd.");
|
||||
IDS.put(11098, "Universal Audio, Inc.");
|
||||
IDS.put(11111, "Lifesize, Inc.");
|
||||
IDS.put(11123, "Pioneer DJ Corporation");
|
||||
IDS.put(11124, "Embedded Intelligence, Inc.");
|
||||
IDS.put(11125, "New Matter");
|
||||
IDS.put(11126, "Shanghai Wingtech Electronic Technology Co., Ltd.");
|
||||
IDS.put(11127, "Epiphan Systems Inc.");
|
||||
IDS.put(11130, "Spin Master Far East Ltd.");
|
||||
IDS.put(11131, "Gigaset Digital Technology (Shenzhen) Co., Ltd.");
|
||||
IDS.put(11132, "Noveltek Semiconductor Corp.");
|
||||
IDS.put(11139, "Silicon Line GmbH");
|
||||
IDS.put(11140, "Ever Win International Corp.");
|
||||
IDS.put(11144, "Socionext Inc.");
|
||||
IDS.put(11145, "Ugreen Group Limited");
|
||||
IDS.put(11146, "Shanghai Pateo Electronic Equipment Mfg. Co., Ltd.");
|
||||
IDS.put(1115, "Renesas Electronics Corp.");
|
||||
IDS.put(11154, "i-BLADES, Inc.");
|
||||
IDS.put(11155, "Altia Systems Inc.");
|
||||
IDS.put(11156, "ShenZhen Baoyuanda Electronics Co., Ltd.");
|
||||
IDS.put(11157, "iST - Integrated Service Technology Inc.");
|
||||
IDS.put(11158, "HYUNDAI MOBIS Co., Ltd.");
|
||||
IDS.put(11161, "360fly, Inc.");
|
||||
IDS.put(11162, "HUIZHOU CHENG SHUO HARDWARE PLASTIC CO., LTD.");
|
||||
IDS.put(11163, "Zhongshan Aute Electronics Technology Co., Ltd.");
|
||||
IDS.put(11164, "Guangdong King Link Industrial Co., Ltd.");
|
||||
IDS.put(11167, "Scietera Technologies, Inc.");
|
||||
IDS.put(11168, "InVue Security Products");
|
||||
IDS.put(11169, "I-Sheng Electric Wire & Cable Co., Ltd.");
|
||||
IDS.put(11170, "China Daheng Group Inc Beijing Image Vision Tech Branch");
|
||||
IDS.put(11171, "Shenzhen FeiTianXia Technology Ltd.");
|
||||
IDS.put(11172, "Shenzhen HengJia New Energy Auto Part Co., Ltd.");
|
||||
IDS.put(11175, "77 Elektronika Kft.");
|
||||
IDS.put(11176, "YUDU EASON ELECTRONIC CO., LTD.");
|
||||
IDS.put(1118, "Microsoft Corporation");
|
||||
IDS.put(11181, "XIN JI (SHENZHEN) COMPUTER PARTS CO., LTD.");
|
||||
IDS.put(11189, "Silk ID Systems");
|
||||
IDS.put(11190, "3D Imaging & Simulations Corp. (3DISC)");
|
||||
IDS.put(11191, "Dongguan ChengXiang Industrial Co., Ltd.");
|
||||
IDS.put(11192, "OCC (Zhuhai) Electronic Co., Ltd.");
|
||||
IDS.put(11194, "Sinseader Electronic Co., Ltd.");
|
||||
IDS.put(11195, "DONGGUAN YELLOWKNIFE Industrial Co., Ltd.");
|
||||
IDS.put(11197, "RF Creations Ltd.");
|
||||
IDS.put(11198, "Chengyi Semiconductors (Shanghai) Co., Ltd.");
|
||||
IDS.put(11199, "Shenzhen Shinning Electronic Co., Ltd.");
|
||||
IDS.put(11200, "Shenzhen WFD Electronics Co., Ltd.");
|
||||
IDS.put(11201, "Dongguan Sino Syncs Industrial Co., Ltd.");
|
||||
IDS.put(11202, "JNTC Co., Ltd.");
|
||||
IDS.put(11208, "DONGGUAN POLIXIN ELECTRIC CO., LTD.");
|
||||
IDS.put(11209, "Tama Electric (Suzhou) Co., Ltd.");
|
||||
IDS.put(1121, "Primax Electronics");
|
||||
IDS.put(11210, "Exvision, Inc.");
|
||||
IDS.put(11216, "mophie, LLC");
|
||||
IDS.put(11219, "Dongguan ULT-unite electronic technology co., LTD");
|
||||
IDS.put(11220, "JL Audio, Inc.");
|
||||
IDS.put(11221, "Cable Matters Inc.");
|
||||
IDS.put(11222, "CoroWare, Inc.");
|
||||
IDS.put(11229, "Charm Sciences Inc.");
|
||||
IDS.put(1123, "EATON");
|
||||
IDS.put(11230, "Pickering Interfaces Limited");
|
||||
IDS.put(11231, "Hangzhou Hikvision Digital Technology Co., Ltd.");
|
||||
IDS.put(11232, "FULLINK ELECTRONICS TECHNOLOGY (SZ) LTD");
|
||||
IDS.put(11233, "AutoChips Inc.");
|
||||
IDS.put(11234, "Electric Connector Technology Co., Ltd.");
|
||||
IDS.put(11237, "LELTEK");
|
||||
IDS.put(11238, "Dongguan KaiWin Electronics Co., Ltd.");
|
||||
IDS.put(11239, "BEFS Co., Ltd.");
|
||||
IDS.put(11240, "Archisite, Inc.");
|
||||
IDS.put(11241, "Magneti Marelli S.p.A Electr BL");
|
||||
IDS.put(11246, "Ventev Mobile");
|
||||
IDS.put(11247, "Quanta Storage Inc.");
|
||||
IDS.put(11248, "Tech-Top Technology Limited");
|
||||
IDS.put(11253, "Shenzhen YOOBAO Technology Co., Ltd.");
|
||||
IDS.put(11254, "Shenzhen Sinotek Technology Co., Ltd.");
|
||||
IDS.put(11255, "KEYW");
|
||||
IDS.put(11256, "Visual Land Inc.");
|
||||
IDS.put(11264, "MEEM SL Ltd");
|
||||
IDS.put(11265, "Dongguan Arin Electronics Technology Co., Ltd.");
|
||||
IDS.put(11266, "DongGuan City JianNuo Electronics Co., Ltd.");
|
||||
IDS.put(11268, "Shenzhen XOX Electronics Co., Ltd.");
|
||||
IDS.put(11269, "Protop International Inc.");
|
||||
IDS.put(11270, "Microsemi Semiconductor (US) Inc.");
|
||||
IDS.put(11271, "Webcloak LLC");
|
||||
IDS.put(11272, "INVECAS INC.");
|
||||
IDS.put(11274, "ATANS Technology Inc.");
|
||||
IDS.put(11275, "Triple Win Precision Technology Co., Ltd.");
|
||||
IDS.put(11276, "IC Realtech");
|
||||
IDS.put(11277, "Embrava Pty Ltd");
|
||||
IDS.put(1128, "Wieson Technologies Co., Ltd.");
|
||||
IDS.put(11280, "Sinotronics Co., Ltd.");
|
||||
IDS.put(11281, "ALLBEST ELECTRONICS TECHNOLOGY CO., LTD.");
|
||||
IDS.put(11282, "Shenzhen Xin Kai Feng Electronics Factory");
|
||||
IDS.put(11283, "MOST WELL Technology Corp.");
|
||||
IDS.put(11284, "Buffalo Memory Co., Ltd.");
|
||||
IDS.put(11285, "Xentris Wireless");
|
||||
IDS.put(11286, "Priferential Accessories Ltd");
|
||||
IDS.put(11289, "Sunlike Technology Co., Ltd.");
|
||||
IDS.put(11290, "Young Fast Optoelectronics Co., Ltd.");
|
||||
IDS.put(11291, "ISAW Camera Inc");
|
||||
IDS.put(11298, "Qanba USA, LLC");
|
||||
IDS.put(11299, "Super Micro Computer Inc.");
|
||||
IDS.put(11302, "Micromax International Corporation");
|
||||
IDS.put(11304, "Granite River Labs Japan Ltd.");
|
||||
IDS.put(11305, "Coagent Enterprise Limited");
|
||||
IDS.put(11306, "LEIA Inc.");
|
||||
IDS.put(11309, "Shenzhen Ebull Technology Limited");
|
||||
IDS.put(1131, "American Megatrends");
|
||||
IDS.put(11310, "Hualun Technology Co., Ltd.");
|
||||
IDS.put(11311, "Sensel, Inc.");
|
||||
IDS.put(11319, "Shenzhen Adition Audio Science & Technology Co., Ltd.");
|
||||
IDS.put(11320, "Goldenconn Electronics Technology (Suzhou) Co., Ltd.");
|
||||
IDS.put(11321, "JIB Electronics Technology Co., Ltd.");
|
||||
IDS.put(11322, "Changzhou Shinco Automotive Electronics Co., Ltd.");
|
||||
IDS.put(11323, "Shenzhen Hangsheng Electronics Corp., Ltd.");
|
||||
IDS.put(11324, "Beartooth Radio, Inc.");
|
||||
IDS.put(11325, "Audience, A Knowles Company");
|
||||
IDS.put(11327, "Nextbit Systems, Inc.");
|
||||
IDS.put(11328, "Leadtrend");
|
||||
IDS.put(11329, "Adaptertek Technology Co., Ltd.");
|
||||
IDS.put(1133, "Logitech Inc.");
|
||||
IDS.put(11330, "Feature Integration Technology Inc.");
|
||||
IDS.put(11331, "Avegant Corporation");
|
||||
IDS.put(11335, "Chunghsin International Electronics Co., Ltd.");
|
||||
IDS.put(11336, "Delphi Electrical Centers (Shanghai) Co., Ltd.");
|
||||
IDS.put(11341, "VVETEK DOO");
|
||||
IDS.put(11347, "Huizhou Foryou General Electronics Co., Ltd.");
|
||||
IDS.put(11348, "LifeWatch Technologies Ltd.");
|
||||
IDS.put(11349, "Magicleap");
|
||||
IDS.put(11355, "Dongguan City Shenglan Electronics Co., LTD.");
|
||||
IDS.put(11356, "Neusoft Corporation");
|
||||
IDS.put(11357, "SIP Simya Electronics Technology Co., Ltd.");
|
||||
IDS.put(11358, "GNSD Automotive Co., Ltd.");
|
||||
IDS.put(11359, "YOODS Co., Ltd.");
|
||||
IDS.put(11360, "Sirin Mobile Technologies AG");
|
||||
IDS.put(11361, "Jadmam Corporation dba: Boytone");
|
||||
IDS.put(11373, "Gibson Innovations");
|
||||
IDS.put(11374, "Shen Zhen Xian Shuo Technology Co. LTD");
|
||||
IDS.put(11375, "PST Eletronica LTDA");
|
||||
IDS.put(11376, "PERI, Inc.");
|
||||
IDS.put(11377, "Bozhou BoTong Information Technology Co., Ltd.");
|
||||
IDS.put(11383, "Profindustry GmbH");
|
||||
IDS.put(11384, "BRAGI GmbH");
|
||||
IDS.put(11385, "WAWGD, Inc. (DBA: Foresight Sports)");
|
||||
IDS.put(11390, "Dongguan Allpass Electronic Co., Ltd.");
|
||||
IDS.put(11391, "SHENZHEN D-VITEC INDUSTRIAL CO., LTD.");
|
||||
IDS.put(11392, "motomobile AG");
|
||||
IDS.put(11393, "Indie Semiconductor");
|
||||
IDS.put(11397, "Audientes");
|
||||
IDS.put(11403, "Huizhou Dehong Technology Co., Ltd.");
|
||||
IDS.put(11404, "PowerCenter Technology Limited");
|
||||
IDS.put(11405, "Mizco International, Inc.");
|
||||
IDS.put(11408, "I. AM. PLUS, LLC");
|
||||
IDS.put(11409, "Corigine, Inc.");
|
||||
IDS.put(11410, "Ningbo Yinzhou Shengke Electronics Co., Ltd.");
|
||||
IDS.put(11417, "Prusa Research s.r.o.");
|
||||
IDS.put(11423, "e-Smart Systems Pvt. Ltd.");
|
||||
IDS.put(11424, "Leagtech Jiangxi Electronic Co., Ltd.");
|
||||
IDS.put(11425, "Dongguan Yujia Electronics Technology Co., Ltd.");
|
||||
IDS.put(11426, "GuangZhou MingPing Electronics Technology");
|
||||
IDS.put(11427, "DJI Technology Co., Ltd.");
|
||||
IDS.put(11428, "Shenzhen Alex Technology Co., Ltd.");
|
||||
IDS.put(11433, "JITS TECHNOLOGY CO., LIMITED");
|
||||
IDS.put(11434, "LIVV Brand llc");
|
||||
IDS.put(11444, "Ava Enterprises, Inc. dba: Boss Audio Systems");
|
||||
IDS.put(11448, "Shenzhen Sydixon Electronic Technology Co., Ltd.");
|
||||
IDS.put(11449, "On-Bright Electronics (Shanghai) Co., Ltd.");
|
||||
IDS.put(11450, "Dongguan Puxu Industrial Co., Ltd.");
|
||||
IDS.put(11451, "Shenzhen Soling Indusrtial Co., Ltd.");
|
||||
IDS.put(11453, "EGGCYTE, INC.");
|
||||
IDS.put(11455, "Donggguan Yuhua Electronic Co., Ltd.");
|
||||
IDS.put(11456, "Hangzhou Zero Zero Technology Co., Ltd.");
|
||||
IDS.put(11462, "Prodigy Technovations Pvt Ltd");
|
||||
IDS.put(11463, "EmergiTech, Inc");
|
||||
IDS.put(11464, "Hewlett Packard Enterprise");
|
||||
IDS.put(11465, "Monolithic Power Systems Inc.");
|
||||
IDS.put(11467, "USB Memory Direct");
|
||||
IDS.put(11468, "Silicon Mitus Inc.");
|
||||
IDS.put(11472, "Technics Global Electronics & JCE Co., Ltd.");
|
||||
IDS.put(11478, "Immersive Media");
|
||||
IDS.put(11479, "Cosemi Technologies Inc.");
|
||||
IDS.put(11481, "Cambrionix Ltd");
|
||||
IDS.put(11482, "CXUN Co. Ltd.");
|
||||
IDS.put(11483, "China Tsp Inc");
|
||||
IDS.put(11490, "Yanfeng Visteon (Chongqing) Automotive Electronics Co");
|
||||
IDS.put(11491, "Alcorlink Corp.");
|
||||
IDS.put(11492, "ISBC Ltd.");
|
||||
IDS.put(11493, "InX8 Inc dba: AKiTiO");
|
||||
IDS.put(11494, "SDAN Tecchnology Co., Ltd.");
|
||||
IDS.put(11495, "Lemobile Information Technology (Beijing) Co., Ltd.");
|
||||
IDS.put(11496, "GongGuan HWX Electronic Technology Co., Ltd.");
|
||||
IDS.put(11497, "Suzhu Jingshi Electronic Technology Co., Ltd.");
|
||||
IDS.put(11498, "Zhong Shan City Richsound Electronic Industrial Ltd.");
|
||||
IDS.put(11499, "Dongguang Kangbang Electronics Co., Ltd.");
|
||||
IDS.put(1151, "Plantronics, Inc.");
|
||||
IDS.put(1154, "Kyocera Corporation");
|
||||
IDS.put(1155, "STMicroelectronics");
|
||||
IDS.put(1161, "Foxconn / Hon Hai");
|
||||
IDS.put(1165, "ITE Tech Inc.");
|
||||
IDS.put(1177, "Yamaha Corporation");
|
||||
IDS.put(1188, "Hitachi, Ltd.");
|
||||
IDS.put(1191, "Visioneer");
|
||||
IDS.put(1193, "Canon Inc.");
|
||||
IDS.put(1200, "Nikon Corporation");
|
||||
IDS.put(1201, "Pan International");
|
||||
IDS.put(1204, "Cypress Semiconductor");
|
||||
IDS.put(1205, "ROHM Co., Ltd.");
|
||||
IDS.put(1207, "Compal Electronics, Inc.");
|
||||
IDS.put(1208, "Seiko Epson Corp.");
|
||||
IDS.put(1211, "I-O Data Device, Inc.");
|
||||
IDS.put(1221, "Fujitsu Ltd.");
|
||||
IDS.put(1227, "FUJIFILM Corporation");
|
||||
IDS.put(1238, "Mentor Graphics");
|
||||
IDS.put(1240, "Microchip Technology Inc.");
|
||||
IDS.put(1241, "Holtek Semiconductor, Inc.");
|
||||
IDS.put(1242, "Panasonic Corporation");
|
||||
IDS.put(1245, "Sharp Corporation");
|
||||
IDS.put(1250, "Exar Corporation");
|
||||
IDS.put(1254, "Identiv, Inc.");
|
||||
IDS.put(1256, "Samsung Electronics Co., Ltd.");
|
||||
IDS.put(1260, "Tokyo Electron Device Limited");
|
||||
IDS.put(1266, "Chicony Electronics Co., Ltd.");
|
||||
IDS.put(1271, "Newnex Technology Corp.");
|
||||
IDS.put(1273, "Brother Industries, Ltd.");
|
||||
IDS.put(1276, "SUNPLUS TECHNOLOGY CO., LTD.");
|
||||
IDS.put(1278, "PFU Limited");
|
||||
IDS.put(1281, "Fujikura/DDK");
|
||||
IDS.put(1282, "Acer, Inc.");
|
||||
IDS.put(1287, "Hosiden Corporation");
|
||||
IDS.put(1293, "Belkin International, Inc.");
|
||||
IDS.put(1300, "FCI Electronics");
|
||||
IDS.put(1302, "Longwell Electronics/Longwell Company");
|
||||
IDS.put(1305, "Star Micronics Co., LTD");
|
||||
IDS.put(1309, "American Power Conversion");
|
||||
IDS.put(1314, "ACON, Advanced-Connectek, Inc.");
|
||||
IDS.put(1343, "Synopsys, Inc.");
|
||||
IDS.put(1356, "Sony Corporation");
|
||||
IDS.put(1360, "Fuji Xerox Co., Ltd.");
|
||||
IDS.put(1367, "ATEN International Co. Ltd.");
|
||||
IDS.put(1369, "Cadence Design Systems, Inc.");
|
||||
IDS.put(1386, "WACOM Co., Ltd.");
|
||||
IDS.put(1389, "EIZO Corporation");
|
||||
IDS.put(1390, "Elecom Co., Ltd.");
|
||||
IDS.put(1394, "Conexant Systems, Inc.");
|
||||
IDS.put(1398, "BAFO/Quality Computer Accessories");
|
||||
IDS.put(1403, "Y-E Data, Inc.");
|
||||
IDS.put(1404, "AVM GmbH");
|
||||
IDS.put(1410, "Roland Corporation");
|
||||
IDS.put(1412, "RATOC Systems, Inc.");
|
||||
IDS.put(1419, "Infineon Technologies");
|
||||
IDS.put(1423, "Alcor Micro, Corp.");
|
||||
IDS.put(1424, "OMRON Corporation");
|
||||
IDS.put(1447, "Bose Corporation");
|
||||
IDS.put(1449, "OmniVision Technologies, Inc.");
|
||||
IDS.put(1452, "Apple");
|
||||
IDS.put(1453, "Y.C. Cable U.S.A., Inc");
|
||||
IDS.put(14627, "National Instruments");
|
||||
IDS.put(1470, "Tyco Electronics Corp., a TE Connectivity Ltd. company");
|
||||
IDS.put(1473, "MegaChips Corporation");
|
||||
IDS.put(1478, "Qualcomm, Inc");
|
||||
IDS.put(1480, "Foxlink/Cheng Uei Precision Industry Co., Ltd.");
|
||||
IDS.put(1482, "Ricoh Company Ltd.");
|
||||
IDS.put(1498, "Microtek International Inc.");
|
||||
IDS.put(1504, "Symbol Technologies");
|
||||
IDS.put(1507, "Genesys Logic, Inc.");
|
||||
IDS.put(1509, "Fuji Electric Co., Ltd.");
|
||||
IDS.put(1525, "Unixtar Technology Inc.");
|
||||
IDS.put(1529, "Datalogic ADC");
|
||||
IDS.put(1535, "LeCroy Corporation");
|
||||
IDS.put(1539, "Novatek Microelectronics Corp.");
|
||||
IDS.put(1545, "SMK Manufacturing Inc.");
|
||||
IDS.put(1551, "Joinsoon Electronics Mfg. Co., Ltd.");
|
||||
IDS.put(1555, "TransAct Technologies Incorporated");
|
||||
IDS.put(1561, "Seiko Instruments Inc.");
|
||||
IDS.put(1582, "JPC/MAIN SUPER Inc.");
|
||||
IDS.put(1583, "Sin Sheng Terminal & Machine Inc.");
|
||||
IDS.put(1593, "Chrontel, Inc.");
|
||||
IDS.put(1611, "Analog Devices, Inc. Development Tools");
|
||||
IDS.put(1612, "Ji-Haw Industrial Co., Ltd");
|
||||
IDS.put(1614, "Suyin Corporation");
|
||||
IDS.put(1621, "Space Shuttle Hi-Tech Co.,Ltd.");
|
||||
IDS.put(1622, "Glory Mark Electronic Ltd.");
|
||||
IDS.put(1623, "Tekcon Electronics Corp.");
|
||||
IDS.put(1624, "Sigma Designs, Inc.");
|
||||
IDS.put(1631, "Good Way Technology Co., Ltd. & GWC technology Inc");
|
||||
IDS.put(1632, "TSAY-E (BVI) International Inc.");
|
||||
IDS.put(1633, "Hamamatsu Photonics K.K.");
|
||||
IDS.put(1642, "Total Technologies, Ltd.");
|
||||
IDS.put(1659, "Prolific Technology, Inc.");
|
||||
IDS.put(16700, "Dell Inc.");
|
||||
IDS.put(1680, "Golden Bridge Electech Inc.");
|
||||
IDS.put(1689, "Tektronix, Inc.");
|
||||
IDS.put(1690, "Askey Computer Corporation");
|
||||
IDS.put(1709, "Greatland Electronics Taiwan Ltd.");
|
||||
IDS.put(1710, "Eurofins Digital Testing Belgium");
|
||||
IDS.put(1720, "Pixela Corporation");
|
||||
IDS.put(1724, "Oki Data Corporation");
|
||||
IDS.put(1727, "Leoco Corporation");
|
||||
IDS.put(1732, "Bizlink Technology, Inc.");
|
||||
IDS.put(1736, "SIIG, Inc.");
|
||||
IDS.put(1747, "Mitsubishi Electric Corporation");
|
||||
IDS.put(1758, "Heisei Technology Co., Ltd.");
|
||||
IDS.put(1802, "Oki Electric Industry Co., Ltd.");
|
||||
IDS.put(1805, "Comoss Electronic Co., Ltd.");
|
||||
IDS.put(1809, "Magic Control Technology Corp.");
|
||||
IDS.put(1816, "Imation Corp.");
|
||||
IDS.put(1838, "Sunix Co., Ltd.");
|
||||
IDS.put(1846, "Lorom Industrial Co., Ltd.");
|
||||
IDS.put(1848, "Mad Catz, Inc.");
|
||||
IDS.put(1899, "HID Global GmbH");
|
||||
IDS.put(1901, "Denso Corporation");
|
||||
IDS.put(1913, "Fairchild Semiconductor");
|
||||
IDS.put(1921, "SanDisk Corporation");
|
||||
IDS.put(1937, "Copartner Technology Corporation");
|
||||
IDS.put(1954, "National Technical Systems");
|
||||
IDS.put(1971, "Plustek, Inc.");
|
||||
IDS.put(1972, "OLYMPUS CORPORATION");
|
||||
IDS.put(1975, "TIME Interconnect Ltd.");
|
||||
IDS.put(1994, "AVerMedia Technologies, Inc.");
|
||||
IDS.put(1999, "Casio Computer Co., Ltd.");
|
||||
IDS.put(2015, "David Electronics Company, Ltd.");
|
||||
IDS.put(2039, "Century Corporation");
|
||||
IDS.put(2058, "Evermuch Technology Co., Ltd.");
|
||||
IDS.put(2101, "Action Star Enterprise Co., Ltd.");
|
||||
IDS.put(2112, "Argosy Research Inc.");
|
||||
IDS.put(2122, "Wipro Limited");
|
||||
IDS.put(2159, "MEC IMEX INC/HPT");
|
||||
IDS.put(2205, "Icron Technologies Corporation");
|
||||
IDS.put(2247, "TAI TWUN ENTERPRISE CO., LTD.");
|
||||
IDS.put(2276, "Pioneer Corporation");
|
||||
IDS.put(2278, "Gemalto SA");
|
||||
IDS.put(2310, "FARADAY Technology Corp.");
|
||||
IDS.put(2313, "Audio-Technica Corp.");
|
||||
IDS.put(2316, "Silicon Motion, Inc. - Taiwan");
|
||||
IDS.put(2334, "Garmin International");
|
||||
IDS.put(2352, "Toshiba Corporation");
|
||||
IDS.put(2362, "Pixart Imaging, Inc.");
|
||||
IDS.put(2363, "Plextor LLC");
|
||||
IDS.put(2366, "J.S.T. Mfg. Co., Ltd.");
|
||||
IDS.put(2385, "Kingston Technology Company");
|
||||
IDS.put(2389, "NVIDIA");
|
||||
IDS.put(2395, "Medialogic Corporation");
|
||||
IDS.put(2397, "Polycom, Inc.");
|
||||
IDS.put(2468, "Contech Research, Inc.");
|
||||
IDS.put(2472, "Lin Shiung Enterprise Co., Ltd.");
|
||||
IDS.put(2475, "Japan Cash Machine Co., Ltd.");
|
||||
IDS.put(2498, "NISCA Corporation");
|
||||
IDS.put(2511, "Electronics Testing Center, Taiwan");
|
||||
IDS.put(2522, "A-FOUR TECH CO., LTD.");
|
||||
IDS.put(2555, "Altera");
|
||||
IDS.put(2578, "Cambridge Silicon Radio Ltd.");
|
||||
IDS.put(2583, "HOYA Corporation");
|
||||
IDS.put(2631, "Hirose Electric Co., Ltd.");
|
||||
IDS.put(2636, "COMPUTEX Co., Ltd.");
|
||||
IDS.put(2640, "Mimaki Engineering Co., Ltd.");
|
||||
IDS.put(2652, "Broadcom Corp.");
|
||||
IDS.put(2667, "Green House Co., Ltd.");
|
||||
IDS.put(2702, "Japan Aviation Electronics Industry Ltd. (JAE)");
|
||||
IDS.put(2727, "Wincor Nixdorf GmbH & Co KG");
|
||||
IDS.put(2733, "Rohde & Schwarz GmbH & Co. KG");
|
||||
IDS.put(2787, "Allion Labs, Inc.");
|
||||
IDS.put(2821, "ASUSTek Computer Inc.");
|
||||
IDS.put(2849, "Yokogawa Electric Corporation");
|
||||
IDS.put(2851, "Pan-Asia Electronics Co., Ltd.");
|
||||
IDS.put(2894, "Musical Electronics Ltd.");
|
||||
IDS.put(2907, "Anritsu Corporation");
|
||||
IDS.put(2922, "Maxim Integrated Products");
|
||||
IDS.put(2965, "ASIX Electronics Corporation");
|
||||
IDS.put(2967, "O2Micro, Inc.");
|
||||
IDS.put(3010, "Seagate Technology LLC");
|
||||
IDS.put(3034, "Realtek Semiconductor Corp.");
|
||||
IDS.put(3035, "Ericsson AB");
|
||||
IDS.put(3044, "Elka International Ltd.");
|
||||
IDS.put(3056, "Pace Micro Technology PLC");
|
||||
IDS.put(3108, "Taiyo Yuden Co., Ltd.");
|
||||
IDS.put(3129, "Aeroflex");
|
||||
IDS.put(3132, "Radius Co., Ltd.");
|
||||
IDS.put(3141, "Sonix Technology Co., Ltd.");
|
||||
IDS.put(3158, "Billion Bright (HK) Corporation Limited");
|
||||
IDS.put(3161, "Dong Guan Shinko Wire Co., Ltd.");
|
||||
IDS.put(3170, "Chant Sincere Co., Ltd");
|
||||
IDS.put(3190, "Solid State System Co., Ltd.");
|
||||
IDS.put(3209, "Honda Tsushin Kogyo Co., Ltd");
|
||||
IDS.put(3245, "Motorola Solutions");
|
||||
IDS.put(3255, "Singatron Enterprise Co. Ltd.");
|
||||
IDS.put(3268, "emsys Embedded Systems GmbH");
|
||||
IDS.put(32902, "Intel Corporation");
|
||||
IDS.put(3294, "Z-Com INC.");
|
||||
IDS.put(3313, "e-CONN ELECTRONIC CO., LTD.");
|
||||
IDS.put(3314, "ENE Technology Inc.");
|
||||
IDS.put(3351, "NALTEC, Inc.");
|
||||
IDS.put(3402, "NF Corporation");
|
||||
IDS.put(3403, "Grape Systems Inc.");
|
||||
IDS.put(3409, "Volex (Asia) Pte Ltd");
|
||||
IDS.put(3425, "MEILU ELECTRONICS (SHENZHEN) CO., LTD.");
|
||||
IDS.put(3441, "Hirakawa Hewtech Corp.");
|
||||
IDS.put(3452, "Taiwan Line Tek Electronic Co., Ltd.");
|
||||
IDS.put(3463, "Dolby Laboratories Inc.");
|
||||
IDS.put(3468, "C-MEDIA ELECTRONICS INC.");
|
||||
IDS.put(3472, "Sure-Fire Electrical Corporation");
|
||||
IDS.put(3495, "IOGEAR, Inc.");
|
||||
IDS.put(3504, "Micro-Star International Co., Ltd.");
|
||||
IDS.put(3537, "Contek Electronics Co., Ltd.");
|
||||
IDS.put(3540, "Custom Engineering SPA");
|
||||
IDS.put(3641, "Smart Modular Technologies, Inc.");
|
||||
IDS.put(3658, "Shenzhen Bao Hing Electric Wire & Cable Mfr. Co.");
|
||||
IDS.put(3673, "Bourns, Inc.");
|
||||
IDS.put(3690, "Megawin Technology Co., Ltd.");
|
||||
IDS.put(3698, "Hsi-Chin Electronics Co., Ltd.");
|
||||
IDS.put(3714, "Ching Tai Electric Wire & Cable Co., Ltd.");
|
||||
IDS.put(3724, "Well Force Electronic Co., Ltd");
|
||||
IDS.put(3725, "MediaTek Inc.");
|
||||
IDS.put(3728, "CRU");
|
||||
IDS.put(3744, "Ours Technology Inc.");
|
||||
IDS.put(3762, "Y-S ELECTRONIC CO., LTD.");
|
||||
IDS.put(3778, "Sweetray Industrial Ltd.");
|
||||
IDS.put(3779, "Axell Corporation");
|
||||
IDS.put(3782, "InnoVISION Multimedia Limited");
|
||||
IDS.put(3790, "TaiSol Electronics Co., Ltd.");
|
||||
IDS.put(3812, "Sunrich Technology (H.K.) Ltd.");
|
||||
IDS.put(3868, "Funai Electric Co., Ltd.");
|
||||
IDS.put(3873, "IOI Technology Corporation");
|
||||
IDS.put(3890, "YFC-BonEagle Electric Co., Ltd.");
|
||||
IDS.put(3896, "Nien-Yi Industrial Corp.");
|
||||
IDS.put(3916, "WORLDWIDE CABLE OPTO CORP.");
|
||||
IDS.put(3923, "Taiyo Cable (Dongguan) Co. Ltd.");
|
||||
IDS.put(3924, "Kawai Musical Instruments Mfg. Co., Ltd.");
|
||||
IDS.put(3936, "GuangZhou Chief Tech Electronic Technology Co. Ltd.");
|
||||
IDS.put(3944, "UQUEST, LTD.");
|
||||
IDS.put(3991, "CviLux Corporation");
|
||||
IDS.put(4003, "Chief Land Electronic Co., Ltd.");
|
||||
IDS.put(4046, "Sony Mobile Communications");
|
||||
IDS.put(4087, "CHI SHING COMPUTER ACCESSORIES CO., LTD.");
|
||||
IDS.put(4096, "Speed Tech Corp.");
|
||||
IDS.put(4100, "LG Electronics Inc.");
|
||||
IDS.put(4101, "Apacer Technology Inc.");
|
||||
IDS.put(4134, "Newly Corporation");
|
||||
IDS.put(4168, "Targus Group International");
|
||||
IDS.put(4172, "AMCO TEC International Inc.");
|
||||
IDS.put(4183, "ON Semiconductor");
|
||||
IDS.put(4184, "Western Digital Technologies, Inc.");
|
||||
IDS.put(4227, "CANON ELECTRONICS INC.");
|
||||
IDS.put(4235, "Grand-tek Technology Co., Ltd.");
|
||||
IDS.put(4236, "Robert Bosch GmbH");
|
||||
IDS.put(4238, "Lotes Co., Ltd.");
|
||||
IDS.put(4266, "Cables To Go");
|
||||
IDS.put(4267, "Universal Global Scientific Industrial Co., Ltd.");
|
||||
IDS.put(4292, "Silicon Laboratories, Inc.");
|
||||
IDS.put(4301, "Kycon Inc.");
|
||||
IDS.put(4362, "Moxa Inc.");
|
||||
IDS.put(4370, "Golden Bright (Sichuan) Electronic Technology Co Ltd");
|
||||
IDS.put(4382, "VSO ELECTRONICS CO., LTD.");
|
||||
IDS.put(4398, "Master Hill Electric Wire and Cable Co., Ltd.");
|
||||
IDS.put(4477, "Santa Electronic Inc.");
|
||||
IDS.put(4505, "Sierra Wireless Inc.");
|
||||
IDS.put(4522, "GlobalMedia Group, LLC");
|
||||
IDS.put(4528, "ATECH FLASH TECHNOLOGY");
|
||||
IDS.put(4643, "SKYCABLE ENTERPRISE CO., LTD.");
|
||||
IDS.put(4703, "ADATA Technology Co., Ltd.");
|
||||
IDS.put(4716, "Aristocrat Technologies");
|
||||
IDS.put(4717, "Bel Stewart");
|
||||
IDS.put(4742, "MARVELL SEMICONDUCTOR, INC.");
|
||||
IDS.put(4756, "RISO KAGAKU CORP.");
|
||||
IDS.put(4792, "Zhejiang Xinya Electronic Technology Co., Ltd.");
|
||||
IDS.put(4817, "Huawei Technologies Co., Ltd.");
|
||||
IDS.put(4823, "Better Holdings (HK) Limited");
|
||||
IDS.put(4907, "Konica Minolta, Inc.");
|
||||
IDS.put(4925, "Jasco Products Company");
|
||||
IDS.put(4989, "Pericom Semiconductor Corp.");
|
||||
IDS.put(5008, "TomTom International B.V.");
|
||||
IDS.put(5075, "AzureWave Technologies, Inc.");
|
||||
IDS.put(5117, "Initio Corporation");
|
||||
IDS.put(5118, "Phison Electronics Corp.");
|
||||
IDS.put(5134, "Telechips, Inc.");
|
||||
IDS.put(5145, "ABILITY ENTERPRISE CO., LTD.");
|
||||
IDS.put(5148, "Leviton Manufacturing");
|
||||
IDS.put(5271, "Panstrong Company Ltd.");
|
||||
IDS.put(5293, "CTK Corporation");
|
||||
IDS.put(5296, "StarTech.com Ltd.");
|
||||
IDS.put(5376, "Ellisys");
|
||||
IDS.put(5404, "VeriSilicon Holdings Co., Ltd.");
|
||||
IDS.put(5421, "JMicron Technology Corp.");
|
||||
IDS.put(5422, "HLDS (Hitachi-LG Data Storage, Inc.)");
|
||||
IDS.put(5440, "Phihong Technology Co., Ltd.");
|
||||
IDS.put(5451, "PNY Technologies Inc.");
|
||||
IDS.put(5453, "Rapid Conn, Connect County Holdings Bhd");
|
||||
IDS.put(5454, "D & M Holdings, Inc.");
|
||||
IDS.put(5480, "Sunf Pu Technology Co., Ltd");
|
||||
IDS.put(5488, "ALLTOP TECHNOLOGY CO., LTD.");
|
||||
IDS.put(5510, "Palconn Technology Co., Ltd.");
|
||||
IDS.put(5528, "Kunshan Guoji Electronics Co., Ltd.");
|
||||
IDS.put(5546, "DongGuan Ya Lian Electronics Co., Ltd.");
|
||||
IDS.put(5645, "Samtec");
|
||||
IDS.put(5694, "HongLin Electronics Co., Ltd.");
|
||||
IDS.put(5753, "Total Phase");
|
||||
IDS.put(5766, "ZOOM Corporation");
|
||||
IDS.put(5836, "silex technology, Inc.");
|
||||
IDS.put(5946, "F. Hoffmann-La Roche AG");
|
||||
IDS.put(5960, "MQP Electronics Ltd.");
|
||||
IDS.put(5964, "ASMedia Technology Inc.");
|
||||
IDS.put(5998, "UD electronic corp.");
|
||||
IDS.put(6001, "Shenzhen Alex Connector Co., Ltd.");
|
||||
IDS.put(6002, "System Level Solutions, Inc.");
|
||||
IDS.put(6018, "Spreadtrum Hong Kong Limited");
|
||||
IDS.put(6024, "ShenZhen Litkconn Technology Co., Ltd.");
|
||||
IDS.put(6053, "Advanced Connection Technology Inc.");
|
||||
IDS.put(6095, "Hip Hing Cable & Plug Mfy. Ltd.");
|
||||
IDS.put(6121, "DisplayLink (UK) Ltd.");
|
||||
IDS.put(6127, "Lenovo");
|
||||
IDS.put(6133, "K.K. Rocky");
|
||||
IDS.put(6160, "Wanshih Electronic Co., Ltd.");
|
||||
IDS.put(6185, "Dongguan YuQiu Electronics Co., Ltd.");
|
||||
IDS.put(6193, "Gwo Jinn Industries Co., Ltd.");
|
||||
IDS.put(6297, "Linkiss Co., Ltd.");
|
||||
IDS.put(6353, "Google Inc.");
|
||||
IDS.put(6394, "Kuang Ying Computer Equipment Co., Ltd.");
|
||||
IDS.put(6421, "Nordic Semiconductor ASA");
|
||||
IDS.put(6448, "Shenzhen Xianhe Technology Co., Ltd.");
|
||||
IDS.put(6449, "Ningbo Broad Telecommunication Co., Ltd.");
|
||||
IDS.put(6470, "Irisguard UK Ltd");
|
||||
IDS.put(6473, "Lab126");
|
||||
IDS.put(6481, "Hyperstone GmbH");
|
||||
IDS.put(6487, "BIOS Corporation");
|
||||
IDS.put(6626, "Solomon Systech Limited");
|
||||
IDS.put(6639, "Pak Heng Technology (Shenzhen) Co., Ltd.");
|
||||
IDS.put(6655, "Best Buy China Ltd.");
|
||||
IDS.put(6666, "USB-IF non-workshop");
|
||||
IDS.put(6709, "Artesyn Technologies Inc.");
|
||||
IDS.put(6720, "TERMINUS TECHNOLOGY INC.");
|
||||
IDS.put(6766, "Global Unichip Corp.");
|
||||
IDS.put(6786, "Proconn Technology Co., Ltd.");
|
||||
IDS.put(6794, "Simula Technology Inc.");
|
||||
IDS.put(6795, "SGS Taiwan Ltd.");
|
||||
IDS.put(6830, "Johnson Component & Equipments Co., Ltd.");
|
||||
IDS.put(6834, "Allied Vision Technologies GmbH");
|
||||
IDS.put(6859, "Salcomp Plc");
|
||||
IDS.put(6865, "Desan Wire Co., Ltd.");
|
||||
IDS.put(6944, "MStar Semiconductor, Inc.");
|
||||
IDS.put(6984, "Plastron Precision Co., Ltd.");
|
||||
IDS.put(7013, "The Hong Kong Standards and Testing Centre Ltd.");
|
||||
IDS.put(7048, "ShenMing Electron (Dong Guan) Co., Ltd.");
|
||||
IDS.put(7086, "Vuzix Corporation");
|
||||
IDS.put(7108, "Ford Motor Co.");
|
||||
IDS.put(7118, "Contac Cable Industrial Limited");
|
||||
IDS.put(7119, "Sunplus Innovation Technology Inc.");
|
||||
IDS.put(7120, "Hangzhou Riyue Electronics Co., Ltd.");
|
||||
IDS.put(7158, "Orient Semiconductor Electronics, Ltd.");
|
||||
IDS.put(7207, "SHENZHEN DNS INDUSTRIES CO., LTD.");
|
||||
IDS.put(7217, "LS Mtron Ltd.");
|
||||
IDS.put(7229, "NONIN MEDICAL INC.");
|
||||
IDS.put(7275, "Philips & Lite-ON Digital Solutions Corporation");
|
||||
IDS.put(7310, "ASTRON INTERNATIONAL CORP.");
|
||||
IDS.put(7320, "ALPINE ELECTRONICS, INC.");
|
||||
IDS.put(7347, "Aces Electronics Co., Ltd.");
|
||||
IDS.put(7348, "OPEX CORPORATION");
|
||||
IDS.put(7390, "Telecommunications Technology Association (TTA)");
|
||||
IDS.put(7434, "Visteon Corporation");
|
||||
IDS.put(7465, "Horng Tong Enterprise Co., Ltd.");
|
||||
IDS.put(7501, "Pegatron Corporation");
|
||||
IDS.put(7516, "Fresco Logic Inc.");
|
||||
IDS.put(7529, "Walta Electronic Co., Ltd.");
|
||||
IDS.put(7543, "Yueqing Changling Electronic Instrument Corp., Ltd.");
|
||||
IDS.put(7584, "Parade Technologies, Inc.");
|
||||
IDS.put(7647, "L&T Technology Services");
|
||||
IDS.put(7649, "Actions Microelectronics Co., Ltd.");
|
||||
IDS.put(7666, "China Telecommunication Technology Labs - Terminals");
|
||||
IDS.put(7668, "SHEN ZHEN FORMAN PRECISION INDUSTRY CO., LTD.");
|
||||
IDS.put(7682, "GLOBEMASTER TECHNOLOGIES CO., LTD.");
|
||||
IDS.put(7696, "Point Grey Research Inc.");
|
||||
IDS.put(7751, "HUNG TA H.T.ENTERPRISE CO., LTD.");
|
||||
IDS.put(7758, "Etron Technology, Inc.");
|
||||
IDS.put(7795, "COMLINK ELECTRONICS CO., LTD.");
|
||||
IDS.put(7818, "HIBEST Electronic (DongGuan) Co., Ltd.");
|
||||
IDS.put(7825, "Other World Computing");
|
||||
IDS.put(7863, "WIN WIN PRECISION INDUSTRIAL CO., LTD.");
|
||||
IDS.put(7879, "Gefen Inc.");
|
||||
IDS.put(7881, "MOSER BAER INDIA LIMITED");
|
||||
IDS.put(7898, "AIRTIES WIRELESS NETWORKS");
|
||||
IDS.put(7956, "Astoria Networks GmbH");
|
||||
IDS.put(7969, "Scosche Industries");
|
||||
IDS.put(7976, "Cal-Comp Electronics & Communications");
|
||||
IDS.put(7977, "Analogix Semiconductor, Inc.");
|
||||
IDS.put(7989, "Amphenol ShouhMin Industry (ShenZhen) Co., Ltd");
|
||||
IDS.put(7996, "Chang Yang Electronics Company Ltd.");
|
||||
IDS.put(8073, "Dongguan Goldconn Electronics Co., Ltd.");
|
||||
IDS.put(8074, "Morning Star Industrial Co., Ltd.");
|
||||
IDS.put(8117, "Unify Software and Solutions GmbH & Co. KG");
|
||||
IDS.put(8137, "NXP Semiconductors");
|
||||
IDS.put(8181, "Changzhou Wujin BEST Electronic Cables Co., Ltd.");
|
||||
IDS.put(8205, "Belkin Electronic (Changzhou) Co., Ltd.");
|
||||
IDS.put(8220, "Freeport Resources Enterprises Corp.");
|
||||
IDS.put(8222, "Qingdao Haier Telecom Co., Ltd.");
|
||||
IDS.put(8284, "Shenzhen Tronixin Electronics Co., Ltd.");
|
||||
IDS.put(8294, "Unicorn Electronics Components Co., Ltd.");
|
||||
IDS.put(8334, "Luxshare-ICT");
|
||||
IDS.put(8341, "CE LINK LIMITED");
|
||||
IDS.put(8342, "Microconn Electronic Co., Ltd.");
|
||||
IDS.put(8367, "Shenzhen CARVE Electronics Co., Ltd.");
|
||||
IDS.put(8382, "BURY GmbH & Co. KG");
|
||||
IDS.put(8384, "FENGHUA KINGSUN CO., LTD.");
|
||||
IDS.put(8386, "Sumitomo Electric Ind., Ltd., Optical Comm. R&D Lab");
|
||||
IDS.put(8439, "XIMEA s.r.o.");
|
||||
IDS.put(8457, "VIA Labs, Inc.");
|
||||
IDS.put(8492, "Shenzhen Linoya Electronic Co., Ltd.");
|
||||
IDS.put(8494, "Amphenol AssembleTech (Xiamen) Co., Ltd.");
|
||||
IDS.put(8524, "Y Soft Corporation");
|
||||
IDS.put(8550, "JVC KENWOOD Corporation");
|
||||
IDS.put(8564, "Transcend Information, Inc.");
|
||||
IDS.put(8566, "TMC/Allion Test Labs");
|
||||
IDS.put(8613, "Genesis Technology USA, Inc.");
|
||||
IDS.put(8627, "Dongguan Teconn Electronics Technology Co., Ltd.");
|
||||
IDS.put(8644, "Netcom Technology (HK) Limited");
|
||||
IDS.put(8659, "Compupack Technology Co., Ltd.");
|
||||
IDS.put(8667, "G-Max Technology Co., Ltd.");
|
||||
IDS.put(8679, "Sagemcom Broadband SAS");
|
||||
IDS.put(8695, "Wuerth-Elektronik eiSos GmbH & Co. KG");
|
||||
IDS.put(8707, "Shin Shin Co., Ltd.");
|
||||
IDS.put(8709, "3eYamaichi Electronics Co., Ltd.");
|
||||
IDS.put(8710, "Wiretek International Investment Ltd.");
|
||||
IDS.put(8711, "Fuzhou Rockchip Electronics Co., Ltd.");
|
||||
IDS.put(8752, "Plugable Technologies");
|
||||
IDS.put(8756, "T-CONN PRECISION CORPORATION");
|
||||
IDS.put(8831, "Granite River Labs");
|
||||
IDS.put(8842, "Hotron Precision Electronic Ind. Corp.");
|
||||
IDS.put(8875, "Trigence Semiconductor, Inc.");
|
||||
IDS.put(8888, "Motorola Mobility Inc.");
|
||||
IDS.put(8904, "Karming Electronic (Shenzhen) Co., Ltd.");
|
||||
IDS.put(8981, "Avery Design Systems, Inc.");
|
||||
IDS.put(8993, "iKingdom Corp. (d.b.a. iConnectivity)");
|
||||
IDS.put(9051, "KangXiang Electronic Co., Ltd.");
|
||||
IDS.put(9068, "ZheJiang Chunsheng Electronics Co., Ltd.");
|
||||
IDS.put(9130, "DOK (HK) Trading Limited");
|
||||
IDS.put(9132, "Marunix Electron Limited");
|
||||
IDS.put(9165, "Avconn Precise Connector Co., Ltd.");
|
||||
IDS.put(9184, "BitifEye Digital Test Solutions GmbH");
|
||||
IDS.put(9205, "Speed Conn Co., Ltd.");
|
||||
IDS.put(9222, "INSIDE Secure");
|
||||
IDS.put(9292, "Minebea Co., Ltd.");
|
||||
IDS.put(9299, "BAANTO");
|
||||
IDS.put(9338, "Suzhou Jutze Technologies Co., Ltd");
|
||||
IDS.put(9355, "DONGGUAN SYNCONN PRECISION INDUSTRY CO. LTD.");
|
||||
IDS.put(9382, "Shenzhen Pangngai Industrial Co., Ltd.");
|
||||
IDS.put(9422, "Shenzhen Deren Electronic Co., Ltd.");
|
||||
IDS.put(9424, "Smith Micro Software, Inc.");
|
||||
IDS.put(9453, "ZEN FACTORY GROUP (ASIA) LTD.");
|
||||
IDS.put(9481, "Chain-In Electronic Co., Ltd.");
|
||||
IDS.put(9514, "SUZHOU KELI TECHNOLOGY DEVELOPMENT CO., LTD.");
|
||||
IDS.put(9515, "TOP Exactitude Industry (ShenZhen) Co., Ltd.");
|
||||
IDS.put(9525, "ShenZhen Hogend Precision Technology Co., Ltd.");
|
||||
IDS.put(9527, "Norel Systems Ltd.");
|
||||
IDS.put(9556, "ASSA ABLOY AB");
|
||||
IDS.put(9575, "DongGuan LongTao Electronic Co., Ltd.");
|
||||
IDS.put(9577, "DongGuan City MingJi Electronics Co., Ltd.");
|
||||
IDS.put(9589, "Weida Hi-Tech Co., Ltd.");
|
||||
IDS.put(9593, "Dongguan Wisechamp Electronic Co., Ltd.");
|
||||
IDS.put(9613, "Sequans Communications");
|
||||
IDS.put(9636, "ALGOLTEK, INC.");
|
||||
IDS.put(9651, "DongGuan Elinke Industrial Co., Ltd.");
|
||||
IDS.put(9679, "Corning Optical Communications LLC");
|
||||
IDS.put(9714, "Dongguan Jinyue Electronics Co., Ltd.");
|
||||
IDS.put(9723, "RICOH IMAGING COMPANY, LTD.");
|
||||
IDS.put(9742, "DongGuan HYX Industrial Co., Ltd.");
|
||||
IDS.put(9753, "Advanced Silicon SA");
|
||||
IDS.put(9756, "EISST Limited");
|
||||
IDS.put(9771, "YTOP Electronics Technical (Kunshan) Co., Ltd.");
|
||||
IDS.put(9841, "Innovative Logic");
|
||||
IDS.put(9842, "GoPro");
|
||||
IDS.put(9846, "Basler AG");
|
||||
IDS.put(9851, "Palpilot International Corp.");
|
||||
IDS.put(9896, "UNIREX CORPORATION");
|
||||
IDS.put(9917, "Integral Memory Plc.");
|
||||
IDS.put(9973, "Morning Star Digital Connector Co., Ltd.");
|
||||
IDS.put(9984, "MITACHI CO., LTD.");
|
||||
IDS.put(9999, "HGST, a Western Digital Company");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,321 +0,0 @@
|
||||
package com.serenegiant.usb.common;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.mogo.usbcamera.R;
|
||||
import com.serenegiant.dialog.MessageDialogFragmentV4;
|
||||
import com.serenegiant.utils.BuildCheck;
|
||||
import com.serenegiant.utils.HandlerThreadHandler;
|
||||
import com.serenegiant.utils.PermissionCheck;
|
||||
|
||||
/**
|
||||
* Created by saki on 2016/11/18.
|
||||
*
|
||||
*/
|
||||
public class BaseActivity extends AppCompatActivity
|
||||
implements MessageDialogFragmentV4.MessageDialogListener {
|
||||
|
||||
private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること
|
||||
private static final String TAG = BaseActivity.class.getSimpleName();
|
||||
|
||||
/** UI操作のためのHandler */
|
||||
private final Handler mUIHandler = new Handler(Looper.getMainLooper());
|
||||
private final Thread mUiThread = mUIHandler.getLooper().getThread();
|
||||
/** ワーカースレッド上で処理するためのHandler */
|
||||
private Handler mWorkerHandler;
|
||||
private long mWorkerThreadID = -1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// ワーカースレッドを生成
|
||||
if (mWorkerHandler == null) {
|
||||
mWorkerHandler = HandlerThreadHandler.createHandler(TAG);
|
||||
mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
clearToast();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void onDestroy() {
|
||||
// ワーカースレッドを破棄
|
||||
if (mWorkerHandler != null) {
|
||||
try {
|
||||
mWorkerHandler.getLooper().quit();
|
||||
} catch (final Exception e) {
|
||||
//
|
||||
}
|
||||
mWorkerHandler = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/**
|
||||
* UIスレッドでRunnableを実行するためのヘルパーメソッド
|
||||
* @param task
|
||||
* @param duration
|
||||
*/
|
||||
public final void runOnUiThread(final Runnable task, final long duration) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mUIHandler.removeCallbacks(task);
|
||||
if ((duration > 0) || Thread.currentThread() != mUiThread) {
|
||||
mUIHandler.postDelayed(task, duration);
|
||||
} else {
|
||||
try {
|
||||
task.run();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する
|
||||
* @param task
|
||||
*/
|
||||
public final void removeFromUiThread(final Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mUIHandler.removeCallbacks(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* ワーカースレッド上で指定したRunnableを実行する
|
||||
* 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される)
|
||||
* @param task
|
||||
* @param delayMillis
|
||||
*/
|
||||
protected final synchronized void queueEvent(final Runnable task, final long delayMillis) {
|
||||
if ((task == null) || (mWorkerHandler == null)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mWorkerHandler.removeCallbacks(task);
|
||||
if (delayMillis > 0) {
|
||||
mWorkerHandler.postDelayed(task, delayMillis);
|
||||
} else if (mWorkerThreadID == Thread.currentThread().getId()) {
|
||||
task.run();
|
||||
} else {
|
||||
mWorkerHandler.post(task);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする
|
||||
* @param task
|
||||
*/
|
||||
protected final synchronized void removeEvent(final Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mWorkerHandler.removeCallbacks(task);
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
private Toast mToast;
|
||||
/**
|
||||
* Toastでメッセージを表示
|
||||
* @param msg
|
||||
*/
|
||||
protected void showToast(@StringRes final int msg, final Object... args) {
|
||||
removeFromUiThread(mShowToastTask);
|
||||
mShowToastTask = new ShowToastTask(msg, args);
|
||||
runOnUiThread(mShowToastTask, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toastが表示されていればキャンセルする
|
||||
*/
|
||||
protected void clearToast() {
|
||||
removeFromUiThread(mShowToastTask);
|
||||
mShowToastTask = null;
|
||||
try {
|
||||
if (mToast != null) {
|
||||
mToast.cancel();
|
||||
mToast = null;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private ShowToastTask mShowToastTask;
|
||||
private final class ShowToastTask implements Runnable {
|
||||
final int msg;
|
||||
final Object args;
|
||||
private ShowToastTask(@StringRes final int msg, final Object... args) {
|
||||
this.msg = msg;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (mToast != null) {
|
||||
mToast.cancel();
|
||||
mToast = null;
|
||||
}
|
||||
final String _msg = (args != null) ? getString(msg, args) : getString(msg);
|
||||
mToast = Toast.makeText(BaseActivity.this, _msg, Toast.LENGTH_SHORT);
|
||||
mToast.show();
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/**
|
||||
* MessageDialogFragmentメッセージダイアログからのコールバックリスナー
|
||||
* @param dialog
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param result
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onMessageDialogResult(final MessageDialogFragmentV4 dialog, final int requestCode, final String[] permissions, final boolean result) {
|
||||
if (result) {
|
||||
// メッセージダイアログでOKを押された時はパーミッション要求する
|
||||
if (BuildCheck.isMarshmallow()) {
|
||||
requestPermissions(permissions, requestCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// メッセージダイアログでキャンセルされた時とAndroid6でない時は自前でチェックして#checkPermissionResultを呼び出す
|
||||
for (final String permission: permissions) {
|
||||
checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(this, permission));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パーミッション要求結果を受け取るためのメソッド
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 何もしてないけど一応呼んどく
|
||||
final int n = Math.min(permissions.length, grantResults.length);
|
||||
for (int i = 0; i < n; i++) {
|
||||
checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パーミッション要求の結果をチェック
|
||||
* ここではパーミッションを取得できなかった時にToastでメッセージ表示するだけ
|
||||
* @param requestCode
|
||||
* @param permission
|
||||
* @param result
|
||||
*/
|
||||
protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) {
|
||||
// パーミッションがないときにはメッセージを表示する
|
||||
if (!result && (permission != null)) {
|
||||
if (Manifest.permission.RECORD_AUDIO.equals(permission)) {
|
||||
showToast(R.string.permission_audio);
|
||||
}
|
||||
if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) {
|
||||
showToast(R.string.permission_ext_storage);
|
||||
}
|
||||
if (Manifest.permission.INTERNET.equals(permission)) {
|
||||
showToast(R.string.permission_network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 動的パーミッション要求時の要求コード
|
||||
protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x12345;
|
||||
protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x234567;
|
||||
protected static final int REQUEST_PERMISSION_NETWORK = 0x345678;
|
||||
protected static final int REQUEST_PERMISSION_CAMERA = 0x537642;
|
||||
|
||||
/**
|
||||
* 外部ストレージへの書き込みパーミッションが有るかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
* @return true 外部ストレージへの書き込みパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionWriteExternalStorage() {
|
||||
if (!PermissionCheck.hasWriteExternalStorage(this)) {
|
||||
MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE,
|
||||
R.string.permission_title, R.string.permission_ext_storage_request,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 録音のパーミッションが有るかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
* @return true 録音のパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionAudio() {
|
||||
if (!PermissionCheck.hasAudio(this)) {
|
||||
MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING,
|
||||
R.string.permission_title, R.string.permission_audio_recording_request,
|
||||
new String[]{Manifest.permission.RECORD_AUDIO});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ネットワークアクセスのパーミッションが有るかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
* @return true ネットワークアクセスのパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionNetwork() {
|
||||
if (!PermissionCheck.hasNetwork(this)) {
|
||||
MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_NETWORK,
|
||||
R.string.permission_title, R.string.permission_network_request,
|
||||
new String[]{Manifest.permission.INTERNET});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* カメラアクセスのパーミッションがあるかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
* @return true カメラアクセスのパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionCamera() {
|
||||
if (!PermissionCheck.hasCamera(this)) {
|
||||
MessageDialogFragmentV4.showDialog(this, REQUEST_PERMISSION_CAMERA,
|
||||
R.string.permission_title, R.string.permission_camera_request,
|
||||
new String[]{Manifest.permission.CAMERA});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
package com.serenegiant.usb.common;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Fragment;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.mogo.usbcamera.R;
|
||||
import com.serenegiant.dialog.MessageDialogFragment;
|
||||
import com.serenegiant.utils.BuildCheck;
|
||||
import com.serenegiant.utils.HandlerThreadHandler;
|
||||
import com.serenegiant.utils.PermissionCheck;
|
||||
|
||||
/**
|
||||
* Created by saki on 2016/11/19.
|
||||
*/
|
||||
public class BaseFragment extends Fragment
|
||||
implements MessageDialogFragment.MessageDialogListener {
|
||||
|
||||
private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること
|
||||
private static final String TAG = BaseFragment.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* UI操作のためのHandler
|
||||
*/
|
||||
private final Handler mUIHandler = new Handler(Looper.getMainLooper());
|
||||
private final Thread mUiThread = mUIHandler.getLooper().getThread();
|
||||
/**
|
||||
* ワーカースレッド上で処理するためのHandler
|
||||
*/
|
||||
private Handler mWorkerHandler;
|
||||
private long mWorkerThreadID = -1;
|
||||
|
||||
public BaseFragment() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// ワーカースレッドを生成
|
||||
if (mWorkerHandler == null) {
|
||||
mWorkerHandler = HandlerThreadHandler.createHandler(TAG);
|
||||
mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
clearToast();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onDestroy() {
|
||||
// ワーカースレッドを破棄
|
||||
if (mWorkerHandler != null) {
|
||||
try {
|
||||
mWorkerHandler.getLooper().quit();
|
||||
} catch (final Exception e) {
|
||||
//
|
||||
}
|
||||
mWorkerHandler = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
|
||||
/**
|
||||
* UIスレッドでRunnableを実行するためのヘルパーメソッド
|
||||
*
|
||||
* @param task
|
||||
* @param duration
|
||||
*/
|
||||
public final void runOnUiThread(final Runnable task, final long duration) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mUIHandler.removeCallbacks(task);
|
||||
if ((duration > 0) || Thread.currentThread() != mUiThread) {
|
||||
mUIHandler.postDelayed(task, duration);
|
||||
} else {
|
||||
try {
|
||||
task.run();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する
|
||||
*
|
||||
* @param task
|
||||
*/
|
||||
public final void removeFromUiThread(final Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
mUIHandler.removeCallbacks(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* ワーカースレッド上で指定したRunnableを実行する
|
||||
* 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される)
|
||||
*
|
||||
* @param task
|
||||
* @param delayMillis
|
||||
*/
|
||||
protected final synchronized void queueEvent(final Runnable task, final long delayMillis) {
|
||||
if ((task == null) || (mWorkerHandler == null)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mWorkerHandler.removeCallbacks(task);
|
||||
if (delayMillis > 0) {
|
||||
mWorkerHandler.postDelayed(task, delayMillis);
|
||||
} else if (mWorkerThreadID == Thread.currentThread().getId()) {
|
||||
task.run();
|
||||
} else {
|
||||
mWorkerHandler.post(task);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする
|
||||
*
|
||||
* @param task
|
||||
*/
|
||||
protected final synchronized void removeEvent(final Runnable task) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mWorkerHandler.removeCallbacks(task);
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
private Toast mToast;
|
||||
|
||||
/**
|
||||
* Toastでメッセージを表示
|
||||
*
|
||||
* @param msg
|
||||
*/
|
||||
protected void showToast(@StringRes final int msg, final Object... args) {
|
||||
removeFromUiThread(mShowToastTask);
|
||||
mShowToastTask = new ShowToastTask(msg, args);
|
||||
runOnUiThread(mShowToastTask, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toastが表示されていればキャンセルする
|
||||
*/
|
||||
protected void clearToast() {
|
||||
removeFromUiThread(mShowToastTask);
|
||||
mShowToastTask = null;
|
||||
try {
|
||||
if (mToast != null) {
|
||||
mToast.cancel();
|
||||
mToast = null;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private ShowToastTask mShowToastTask;
|
||||
|
||||
private final class ShowToastTask implements Runnable {
|
||||
final int msg;
|
||||
final Object args;
|
||||
|
||||
private ShowToastTask(@StringRes final int msg, final Object... args) {
|
||||
this.msg = msg;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (mToast != null) {
|
||||
mToast.cancel();
|
||||
mToast = null;
|
||||
}
|
||||
if (args != null) {
|
||||
final String _msg = getString(msg, args);
|
||||
mToast = Toast.makeText(getActivity(), _msg, Toast.LENGTH_SHORT);
|
||||
} else {
|
||||
mToast = Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT);
|
||||
}
|
||||
mToast.show();
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
|
||||
/**
|
||||
* MessageDialogFragmentメッセージダイアログからのコールバックリスナー
|
||||
*
|
||||
* @param dialog
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param result
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onMessageDialogResult(final MessageDialogFragment dialog, final int requestCode, final String[] permissions, final boolean result) {
|
||||
if (result) {
|
||||
// メッセージダイアログでOKを押された時はパーミッション要求する
|
||||
if (BuildCheck.isMarshmallow()) {
|
||||
requestPermissions(permissions, requestCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// メッセージダイアログでキャンセルされた時とAndroid6でない時は自前でチェックして#checkPermissionResultを呼び出す
|
||||
for (final String permission : permissions) {
|
||||
checkPermissionResult(requestCode, permission, PermissionCheck.hasPermission(getActivity(), permission));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パーミッション要求結果を受け取るためのメソッド
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permissions
|
||||
* @param grantResults
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 何もしてないけど一応呼んどく
|
||||
final int n = Math.min(permissions.length, grantResults.length);
|
||||
for (int i = 0; i < n; i++) {
|
||||
checkPermissionResult(requestCode, permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パーミッション要求の結果をチェック
|
||||
* ここではパーミッションを取得できなかった時にToastでメッセージ表示するだけ
|
||||
*
|
||||
* @param requestCode
|
||||
* @param permission
|
||||
* @param result
|
||||
*/
|
||||
protected void checkPermissionResult(final int requestCode, final String permission, final boolean result) {
|
||||
// パーミッションがないときにはメッセージを表示する
|
||||
if (!result && (permission != null)) {
|
||||
if (Manifest.permission.RECORD_AUDIO.equals(permission)) {
|
||||
showToast(R.string.permission_audio);
|
||||
}
|
||||
if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) {
|
||||
showToast(R.string.permission_ext_storage);
|
||||
}
|
||||
if (Manifest.permission.INTERNET.equals(permission)) {
|
||||
showToast(R.string.permission_network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 動的パーミッション要求時の要求コード
|
||||
protected static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 0x12345;
|
||||
protected static final int REQUEST_PERMISSION_AUDIO_RECORDING = 0x234567;
|
||||
protected static final int REQUEST_PERMISSION_NETWORK = 0x345678;
|
||||
protected static final int REQUEST_PERMISSION_CAMERA = 0x537642;
|
||||
|
||||
/**
|
||||
* 外部ストレージへの書き込みパーミッションが有るかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
*
|
||||
* @return true 外部ストレージへの書き込みパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionWriteExternalStorage() {
|
||||
if (!PermissionCheck.hasWriteExternalStorage(getActivity())) {
|
||||
MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE,
|
||||
R.string.permission_title, R.string.permission_ext_storage_request,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 録音のパーミッションが有るかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
*
|
||||
* @return true 録音のパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionAudio() {
|
||||
if (!PermissionCheck.hasAudio(getActivity())) {
|
||||
MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_AUDIO_RECORDING,
|
||||
R.string.permission_title, R.string.permission_audio_recording_request,
|
||||
new String[]{Manifest.permission.RECORD_AUDIO});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ネットワークアクセスのパーミッションが有るかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
*
|
||||
* @return true ネットワークアクセスのパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionNetwork() {
|
||||
if (!PermissionCheck.hasNetwork(getActivity())) {
|
||||
MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_NETWORK,
|
||||
R.string.permission_title, R.string.permission_network_request,
|
||||
new String[]{Manifest.permission.INTERNET});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* カメラアクセスのパーミッションがあるかどうかをチェック
|
||||
* なければ説明ダイアログを表示する
|
||||
*
|
||||
* @return true カメラアクセスのパーミッションが有る
|
||||
*/
|
||||
protected boolean checkPermissionCamera() {
|
||||
if (!PermissionCheck.hasCamera(getActivity())) {
|
||||
MessageDialogFragment.showDialog(this, REQUEST_PERMISSION_CAMERA,
|
||||
R.string.permission_title, R.string.permission_camera_request,
|
||||
new String[]{Manifest.permission.CAMERA});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.serenegiant.usb.common;
|
||||
|
||||
import android.app.Service;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.serenegiant.utils.HandlerThreadHandler;
|
||||
|
||||
public abstract class BaseService extends Service {
|
||||
private static boolean DEBUG = false; // FIXME 実働時はfalseにセットすること
|
||||
private static final String TAG = BaseService.class.getSimpleName();
|
||||
|
||||
/** UI操作のためのHandler */
|
||||
private final Handler mUIHandler = new Handler(Looper.getMainLooper());
|
||||
private final Thread mUiThread = mUIHandler.getLooper().getThread();
|
||||
/** ワーカースレッド上で処理するためのHandler */
|
||||
private Handler mWorkerHandler;
|
||||
private long mWorkerThreadID = -1;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
// ワーカースレッドを生成
|
||||
if (mWorkerHandler == null) {
|
||||
mWorkerHandler = HandlerThreadHandler.createHandler(TAG);
|
||||
mWorkerThreadID = mWorkerHandler.getLooper().getThread().getId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onDestroy() {
|
||||
// ワーカースレッドを破棄
|
||||
if (mWorkerHandler != null) {
|
||||
try {
|
||||
mWorkerHandler.getLooper().quit();
|
||||
} catch (final Exception e) {
|
||||
//
|
||||
}
|
||||
mWorkerHandler = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
//================================================================================
|
||||
/**
|
||||
* UIスレッドでRunnableを実行するためのヘルパーメソッド
|
||||
* @param task
|
||||
* @param duration
|
||||
*/
|
||||
public final void runOnUiThread(final Runnable task, final long duration) {
|
||||
if (task == null) return;
|
||||
mUIHandler.removeCallbacks(task);
|
||||
if ((duration > 0) || Thread.currentThread() != mUiThread) {
|
||||
mUIHandler.postDelayed(task, duration);
|
||||
} else {
|
||||
try {
|
||||
task.run();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UIスレッド上で指定したRunnableが実行待ちしていれば実行待ちを解除する
|
||||
* @param task
|
||||
*/
|
||||
public final void removeFromUiThread(final Runnable task) {
|
||||
if (task == null) return;
|
||||
mUIHandler.removeCallbacks(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* ワーカースレッド上で指定したRunnableを実行する
|
||||
* 未実行の同じRunnableがあればキャンセルされる(後から指定した方のみ実行される)
|
||||
* @param task
|
||||
* @param delayMillis
|
||||
*/
|
||||
protected final synchronized void queueEvent(final Runnable task, final long delayMillis) {
|
||||
if ((task == null) || (mWorkerHandler == null)) return;
|
||||
try {
|
||||
mWorkerHandler.removeCallbacks(task);
|
||||
if (delayMillis > 0) {
|
||||
mWorkerHandler.postDelayed(task, delayMillis);
|
||||
} else if (mWorkerThreadID == Thread.currentThread().getId()) {
|
||||
task.run();
|
||||
} else {
|
||||
mWorkerHandler.post(task);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定したRunnableをワーカースレッド上で実行予定であればキャンセルする
|
||||
* @param task
|
||||
*/
|
||||
protected final synchronized void removeEvent(final Runnable task) {
|
||||
if (task == null) return;
|
||||
try {
|
||||
mWorkerHandler.removeCallbacks(task);
|
||||
} catch (final Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* 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 com.serenegiant.usb.UVCCamera;
|
||||
import com.serenegiant.usb.widget.CameraViewInterface;
|
||||
|
||||
public class UVCCameraHandler extends AbstractUVCCameraHandler {
|
||||
|
||||
/**
|
||||
* create UVCCameraHandler, use MediaVideoEncoder, try MJPEG, default bandwidth
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param width
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandler createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int width, final int height) {
|
||||
|
||||
return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandler, use MediaVideoEncoder, try MJPEG
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param width
|
||||
* @param height
|
||||
* @param bandwidthFactor
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandler createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int width, final int height, final float bandwidthFactor) {
|
||||
|
||||
return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandler, try MJPEG, default bandwidth
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
|
||||
* @param width
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandler createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int encoderType, final int width, final int height) {
|
||||
|
||||
return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandler, default bandwidth
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
|
||||
* @param width
|
||||
* @param height
|
||||
* @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1)
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandler createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int encoderType, final int width, final int height, final int format) {
|
||||
|
||||
return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandler
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
|
||||
* @param width
|
||||
* @param height
|
||||
* @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1)
|
||||
* @param bandwidthFactor
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandler createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) {
|
||||
|
||||
final CameraThread thread = new CameraThread(UVCCameraHandler.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor);
|
||||
thread.start();
|
||||
return (UVCCameraHandler)thread.getHandler();
|
||||
}
|
||||
|
||||
protected UVCCameraHandler(final CameraThread thread) {
|
||||
super(thread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreview(final Object surface) {
|
||||
super.startPreview(surface);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void captureStill() {
|
||||
// super.captureStill();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void captureStill(final String path,OnCaptureListener listener) {
|
||||
super.captureStill(path,listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCameraFoucs() {
|
||||
super.startCameraFoucs();
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* 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.UVCCamera;
|
||||
import com.serenegiant.usb.widget.CameraViewInterface;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class UVCCameraHandlerMultiSurface extends AbstractUVCCameraHandler {
|
||||
/**
|
||||
* create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG, default bandwidth
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param width
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandlerMultiSurface createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int width, final int height) {
|
||||
|
||||
return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandlerMultiSurface, use MediaVideoEncoder, try MJPEG
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param width
|
||||
* @param height
|
||||
* @param bandwidthFactor
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandlerMultiSurface createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int width, final int height, final float bandwidthFactor) {
|
||||
|
||||
return createHandler(parent, cameraView, 1, width, height, UVCCamera.FRAME_FORMAT_MJPEG, bandwidthFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandlerMultiSurface, try MJPEG, default bandwidth
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param encoderType
|
||||
* @param width
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandlerMultiSurface createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int encoderType, final int width, final int height) {
|
||||
|
||||
return createHandler(parent, cameraView, encoderType, width, height, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandlerMultiSurface, default bandwidth
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param encoderType
|
||||
* @param width
|
||||
* @param height
|
||||
* @param format
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandlerMultiSurface createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int encoderType, final int width, final int height, final int format) {
|
||||
|
||||
return createHandler(parent, cameraView, encoderType, width, height, format, UVCCamera.DEFAULT_BANDWIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UVCCameraHandlerMultiSurface
|
||||
* @param parent
|
||||
* @param cameraView
|
||||
* @param encoderType 0: use MediaSurfaceEncoder, 1: use MediaVideoEncoder, 2: use MediaVideoBufferEncoder
|
||||
* @param width
|
||||
* @param height
|
||||
* @param format either UVCCamera.FRAME_FORMAT_YUYV(0) or UVCCamera.FRAME_FORMAT_MJPEG(1)
|
||||
* @param bandwidthFactor
|
||||
* @return
|
||||
*/
|
||||
public static final UVCCameraHandlerMultiSurface createHandler(
|
||||
final Activity parent, final CameraViewInterface cameraView,
|
||||
final int encoderType, final int width, final int height, final int format, final float bandwidthFactor) {
|
||||
|
||||
final CameraThread thread = new CameraThread(UVCCameraHandlerMultiSurface.class, parent, cameraView, encoderType, width, height, format, bandwidthFactor);
|
||||
thread.start();
|
||||
return (UVCCameraHandlerMultiSurface)thread.getHandler();
|
||||
}
|
||||
|
||||
private RendererHolder mRendererHolder;
|
||||
protected UVCCameraHandlerMultiSurface(final CameraThread thread) {
|
||||
super(thread);
|
||||
mRendererHolder = new RendererHolder(thread.getWidth(), thread.getHeight(), null);
|
||||
}
|
||||
|
||||
public synchronized void release() {
|
||||
if (mRendererHolder != null) {
|
||||
mRendererHolder.release();
|
||||
mRendererHolder = null;
|
||||
}
|
||||
super.release();
|
||||
}
|
||||
|
||||
public synchronized void resize(final int width, final int height) {
|
||||
super.resize(width, height);
|
||||
if (mRendererHolder != null) {
|
||||
mRendererHolder.resize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void startPreview() {
|
||||
checkReleased();
|
||||
if (mRendererHolder != null) {
|
||||
super.startPreview(mRendererHolder.getSurface());
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void addSurface(final int surfaceId, final Surface surface, final boolean isRecordable) {
|
||||
checkReleased();
|
||||
mRendererHolder.addSurface(surfaceId, surface, isRecordable);
|
||||
}
|
||||
|
||||
public synchronized void removeSurface(final int surfaceId) {
|
||||
if (mRendererHolder != null) {
|
||||
mRendererHolder.removeSurface(surfaceId);
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void captureStill() {
|
||||
// checkReleased();
|
||||
// super.captureStill();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void captureStill(final String path,OnCaptureListener listener) {
|
||||
checkReleased();
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (UVCCameraHandlerMultiSurface.this) {
|
||||
if (mRendererHolder != null) {
|
||||
try {
|
||||
mRendererHolder.captureStill(path);
|
||||
updateMedia(path);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
public boolean frameAvailableSoon();
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaRecorder;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class MediaAudioEncoder extends MediaEncoder implements IAudioEncoder {
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "MediaAudioEncoder";
|
||||
|
||||
private static final String MIME_TYPE = "audio/mp4a-latm";
|
||||
public static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices.
|
||||
private static final int BIT_RATE = 64000;
|
||||
public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel
|
||||
public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec
|
||||
|
||||
private AudioThread mAudioThread = null;
|
||||
|
||||
public MediaAudioEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) {
|
||||
super(muxer, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepare() throws IOException {
|
||||
if (DEBUG) Log.v(TAG, "prepare:");
|
||||
mTrackIndex = -1;
|
||||
mMuxerStarted = mIsEOS = false;
|
||||
// prepare MediaCodec for AAC encoding of audio data from inernal mic.
|
||||
final MediaCodecInfo audioCodecInfo = selectAudioCodec(MIME_TYPE);
|
||||
if (audioCodecInfo == null) {
|
||||
Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.i(TAG, "selected codec: " + audioCodecInfo.getName());
|
||||
|
||||
final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1);
|
||||
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
||||
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
|
||||
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
|
||||
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
|
||||
// audioFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, inputFile.length());
|
||||
// audioFormat.setLong(MediaFormat.KEY_DURATION, (long)durationInMs );
|
||||
if (DEBUG) Log.i(TAG, "format: " + audioFormat);
|
||||
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
|
||||
mMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
mMediaCodec.start();
|
||||
if (DEBUG) Log.i(TAG, "prepare finishing");
|
||||
if (mListener != null) {
|
||||
try {
|
||||
mListener.onPrepared(this);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "prepare:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startRecording() {
|
||||
super.startRecording();
|
||||
// create and execute audio capturing thread using internal mic
|
||||
if (mAudioThread == null) {
|
||||
mAudioThread = new AudioThread();
|
||||
mAudioThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void release() {
|
||||
mAudioThread = null;
|
||||
super.release();
|
||||
}
|
||||
|
||||
private static final int[] AUDIO_SOURCES = new int[] {
|
||||
MediaRecorder.AudioSource.DEFAULT,
|
||||
MediaRecorder.AudioSource.MIC,
|
||||
MediaRecorder.AudioSource.CAMCORDER,
|
||||
};
|
||||
|
||||
/**
|
||||
* Thread to capture audio data from internal mic as uncompressed 16bit PCM data
|
||||
* and write them to the MediaCodec encoder
|
||||
*/
|
||||
private class AudioThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); // THREAD_PRIORITY_URGENT_AUDIO
|
||||
int cnt = 0;
|
||||
final int min_buffer_size = AudioRecord.getMinBufferSize(
|
||||
SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
|
||||
int buffer_size = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER;
|
||||
if (buffer_size < min_buffer_size)
|
||||
buffer_size = ((min_buffer_size / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2;
|
||||
final ByteBuffer buf = ByteBuffer.allocateDirect(SAMPLES_PER_FRAME).order(ByteOrder.nativeOrder());
|
||||
AudioRecord audioRecord = null;
|
||||
for (final int src: AUDIO_SOURCES) {
|
||||
try {
|
||||
audioRecord = new AudioRecord(src,
|
||||
SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, buffer_size);
|
||||
if (audioRecord != null) {
|
||||
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
audioRecord.release();
|
||||
audioRecord = null;
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
audioRecord = null;
|
||||
}
|
||||
if (audioRecord != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (audioRecord != null) {
|
||||
try {
|
||||
if (mIsCapturing) {
|
||||
if (DEBUG) Log.v(TAG, "AudioThread:start audio recording");
|
||||
int readBytes;
|
||||
audioRecord.startRecording();
|
||||
try {
|
||||
for ( ; mIsCapturing && !mRequestStop && !mIsEOS ; ) {
|
||||
// read audio data from internal mic
|
||||
buf.clear();
|
||||
try {
|
||||
readBytes = audioRecord.read(buf, SAMPLES_PER_FRAME);
|
||||
} catch (final Exception e) {
|
||||
break;
|
||||
}
|
||||
if (readBytes > 0) {
|
||||
// set audio data to encoder
|
||||
buf.position(readBytes);
|
||||
buf.flip();
|
||||
encode(buf, readBytes, getPTSUs());
|
||||
frameAvailableSoon();
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
if (cnt > 0) {
|
||||
frameAvailableSoon();
|
||||
}
|
||||
} finally {
|
||||
audioRecord.stop();
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "AudioThread#run", e);
|
||||
} finally {
|
||||
audioRecord.release();
|
||||
}
|
||||
}
|
||||
if (cnt == 0) {
|
||||
for (int i = 0; mIsCapturing && (i < 5); i++) {
|
||||
buf.position(SAMPLES_PER_FRAME);
|
||||
buf.flip();
|
||||
try {
|
||||
encode(buf, SAMPLES_PER_FRAME, getPTSUs());
|
||||
frameAvailableSoon();
|
||||
} catch (final Exception e) {
|
||||
break;
|
||||
}
|
||||
synchronized(this) {
|
||||
try {
|
||||
wait(50);
|
||||
} catch (final InterruptedException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) Log.v(TAG, "AudioThread:finished");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* select the first codec that match a specific MIME type
|
||||
* @param mimeType
|
||||
* @return
|
||||
*/
|
||||
private static final MediaCodecInfo selectAudioCodec(final String mimeType) {
|
||||
if (DEBUG) Log.v(TAG, "selectAudioCodec:");
|
||||
|
||||
MediaCodecInfo result = null;
|
||||
// get the list of available codecs
|
||||
final int numCodecs = MediaCodecList.getCodecCount();
|
||||
LOOP: for (int i = 0; i < numCodecs; i++) {
|
||||
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
if (!codecInfo.isEncoder()) { // skipp decoder
|
||||
continue;
|
||||
}
|
||||
final String[] types = codecInfo.getSupportedTypes();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
if (DEBUG) Log.i(TAG, "supportedType:" + codecInfo.getName() + ",MIME=" + types[j]);
|
||||
if (types[j].equalsIgnoreCase(mimeType)) {
|
||||
if (result == null) {
|
||||
result = codecInfo;
|
||||
break LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,570 +0,0 @@
|
||||
package com.serenegiant.usb.encoder;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import com.mogo.usbcamera.utils.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class MediaEncoder implements Runnable {
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "MediaEncoder";
|
||||
public static final int TYPE_AUDIO = 0; // 音频数据
|
||||
public static final int TYPE_VIDEO = 1; // 视频数据
|
||||
|
||||
protected static final int TIMEOUT_USEC = 10000; // 10毫秒
|
||||
protected static final int MSG_FRAME_AVAILABLE = 1;
|
||||
protected static final int MSG_STOP_RECORDING = 9;
|
||||
private long lastPush;
|
||||
private long millisPerframe;
|
||||
private boolean isExit;
|
||||
|
||||
public interface MediaEncoderListener {
|
||||
void onPrepared(MediaEncoder encoder);
|
||||
void onStopped(MediaEncoder encoder);
|
||||
// 音频或视频流,type=0为音频,type=1为视频
|
||||
void onEncodeResult(byte[] data, int offset,
|
||||
int length, long timestamp, int type);
|
||||
}
|
||||
|
||||
protected final Object mSync = new Object();
|
||||
/**
|
||||
* Flag that indicate this encoder is capturing now.
|
||||
*/
|
||||
protected volatile boolean mIsCapturing;
|
||||
/**
|
||||
* Flag that indicate the frame data will be available soon.
|
||||
*/
|
||||
private int mRequestDrain;
|
||||
/**
|
||||
* Flag to request stop capturing
|
||||
*/
|
||||
protected volatile boolean mRequestStop;
|
||||
/**
|
||||
* Flag that indicate encoder received EOS(End Of Stream)
|
||||
*/
|
||||
protected boolean mIsEOS;
|
||||
/**
|
||||
* Flag the indicate the muxer is running
|
||||
*/
|
||||
protected boolean mMuxerStarted;
|
||||
/**
|
||||
* Track Number
|
||||
*/
|
||||
protected int mTrackIndex;
|
||||
/**
|
||||
* MediaCodec instance for encoding
|
||||
*/
|
||||
protected MediaCodec mMediaCodec; // API >= 16(Android4.1.2)
|
||||
/**
|
||||
* Weak refarence of MediaMuxerWarapper instance
|
||||
*/
|
||||
protected final WeakReference<MediaMuxerWrapper> mWeakMuxer;
|
||||
/**
|
||||
* BufferInfo instance for dequeuing
|
||||
*/
|
||||
private MediaCodec.BufferInfo mBufferInfo; // API >= 16(Android4.1.2)
|
||||
|
||||
protected final MediaEncoderListener mListener;
|
||||
|
||||
/**
|
||||
* There are 13 supported frequencies by ADTS.
|
||||
**/
|
||||
public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0
|
||||
88200, // 1
|
||||
64000, // 2
|
||||
48000, // 3
|
||||
44100, // 4
|
||||
32000, // 5
|
||||
24000, // 6
|
||||
22050, // 7
|
||||
16000, // 8
|
||||
12000, // 9
|
||||
11025, // 10
|
||||
8000, // 11
|
||||
7350, // 12
|
||||
-1, // 13
|
||||
-1, // 14
|
||||
-1, // 15
|
||||
};
|
||||
|
||||
public MediaEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) {
|
||||
if (listener == null) throw new NullPointerException("MediaEncoderListener is null");
|
||||
if (muxer == null) throw new NullPointerException("MediaMuxerWrapper is null");
|
||||
mWeakMuxer = new WeakReference<MediaMuxerWrapper>(muxer);
|
||||
muxer.addEncoder(this);
|
||||
mListener = listener;
|
||||
synchronized (mSync) {
|
||||
// create BufferInfo here for effectiveness(to reduce GC)
|
||||
mBufferInfo = new MediaCodec.BufferInfo();
|
||||
// wait for starting thread
|
||||
new Thread(this, getClass().getSimpleName()).start();
|
||||
try {
|
||||
mSync.wait();
|
||||
} catch (final InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getOutputPath() {
|
||||
final MediaMuxerWrapper muxer = mWeakMuxer.get();
|
||||
return muxer != null ? muxer.getOutputPath() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* the method to indicate frame data is soon available or already available
|
||||
* @return return true if encoder is ready to encod.
|
||||
*/
|
||||
public boolean frameAvailableSoon() {
|
||||
// if (DEBUG) Log.v(TAG, "frameAvailableSoon");
|
||||
synchronized (mSync) {
|
||||
if (!mIsCapturing || mRequestStop) {
|
||||
return false;
|
||||
}
|
||||
mRequestDrain++;
|
||||
mSync.notifyAll();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* encoding loop on private thread
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
// android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
|
||||
synchronized (mSync) {
|
||||
mRequestStop = false;
|
||||
mRequestDrain = 0;
|
||||
mSync.notify();
|
||||
}
|
||||
final boolean isRunning = true;
|
||||
boolean localRequestStop;
|
||||
boolean localRequestDrain;
|
||||
boolean localIsNotExit;
|
||||
// 创建h264
|
||||
FileUtils.createfile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/test222.h264");
|
||||
|
||||
while (isRunning) {
|
||||
synchronized (mSync) {
|
||||
localRequestStop = mRequestStop;
|
||||
localRequestDrain = (mRequestDrain > 0);
|
||||
if (localRequestDrain)
|
||||
mRequestDrain--;
|
||||
}
|
||||
if (localRequestStop) {
|
||||
drain();
|
||||
// request stop recording
|
||||
signalEndOfInputStream();
|
||||
// process output data again for EOS signale
|
||||
drain();
|
||||
// release all related objects
|
||||
release();
|
||||
break;
|
||||
}
|
||||
if (localRequestDrain) {
|
||||
drain();
|
||||
} else {
|
||||
synchronized (mSync) {
|
||||
try {
|
||||
mSync.wait();
|
||||
} catch (final InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end of while
|
||||
synchronized (mSync) {
|
||||
mRequestStop = true;
|
||||
mIsCapturing = false;
|
||||
}
|
||||
FileUtils.releaseFile();
|
||||
}
|
||||
|
||||
/*
|
||||
* prepareing method for each sub class
|
||||
* this method should be implemented in sub class, so set this as abstract method
|
||||
* @throws IOException
|
||||
*/
|
||||
/*package*/ abstract void prepare() throws IOException;
|
||||
|
||||
/*package*/ void startRecording() {
|
||||
if (DEBUG) Log.v(TAG, "startRecording");
|
||||
synchronized (mSync) {
|
||||
mIsCapturing = true;
|
||||
mRequestStop = false;
|
||||
isExit = false;
|
||||
mSync.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the method to request stop encoding
|
||||
*/
|
||||
/*package*/ void stopRecording() {
|
||||
if (DEBUG) Log.v(TAG, "stopRecording");
|
||||
synchronized (mSync) {
|
||||
if (!mIsCapturing || mRequestStop) {
|
||||
return;
|
||||
}
|
||||
mRequestStop = true; // for rejecting newer frame
|
||||
isExit = true;
|
||||
mSync.notifyAll();
|
||||
// We can not know when the encoding and writing finish.
|
||||
// so we return immediately after request to avoid delay of caller thread
|
||||
}
|
||||
}
|
||||
|
||||
//********************************************************************************
|
||||
//********************************************************************************
|
||||
/**
|
||||
* Release all releated objects
|
||||
*/
|
||||
protected void release() {
|
||||
if (DEBUG) Log.d(TAG, "release:");
|
||||
try {
|
||||
mListener.onStopped(this);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "failed onStopped", e);
|
||||
}
|
||||
mIsCapturing = false;
|
||||
if (mMediaCodec != null) {
|
||||
try {
|
||||
mMediaCodec.stop();
|
||||
mMediaCodec.release();
|
||||
mMediaCodec = null;
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "failed releasing MediaCodec", e);
|
||||
}
|
||||
}
|
||||
if (mMuxerStarted) {
|
||||
final MediaMuxerWrapper muxer = mWeakMuxer.get();
|
||||
if (muxer != null) {
|
||||
try {
|
||||
muxer.stop();
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "failed stopping muxer", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
mBufferInfo = null;
|
||||
}
|
||||
|
||||
protected void signalEndOfInputStream() {
|
||||
if (DEBUG) Log.d(TAG, "sending EOS to encoder");
|
||||
// signalEndOfInputStream is only avairable for video encoding with surface
|
||||
// and equivalent sending a empty buffer with BUFFER_FLAG_END_OF_STREAM flag.
|
||||
// mMediaCodec.signalEndOfInputStream(); // API >= 18
|
||||
encode((byte[])null, 0, getPTSUs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set byte array to the MediaCodec encoder
|
||||
* @param buffer
|
||||
* @param length length of byte array, zero means EOS.
|
||||
* @param presentationTimeUs
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void encode(final byte[] buffer, final int length, final long presentationTimeUs) {
|
||||
if (!mIsCapturing) return;
|
||||
int ix = 0, sz;
|
||||
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
|
||||
while (mIsCapturing && ix < length) {
|
||||
final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
|
||||
if (inputBufferIndex >= 0) {
|
||||
final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
|
||||
inputBuffer.clear();
|
||||
sz = inputBuffer.remaining();
|
||||
sz = (ix + sz < length) ? sz : length - ix;
|
||||
if (sz > 0 && (buffer != null)) {
|
||||
inputBuffer.put(buffer, ix, sz);
|
||||
}
|
||||
ix += sz;
|
||||
// if (DEBUG) Log.v(TAG, "encode:queueInputBuffer");
|
||||
if (length <= 0) {
|
||||
// send EOS
|
||||
mIsEOS = true;
|
||||
if (DEBUG) Log.i(TAG, "send BUFFER_FLAG_END_OF_STREAM");
|
||||
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0,
|
||||
presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||
break;
|
||||
} else {
|
||||
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, sz,
|
||||
presentationTimeUs, 0);
|
||||
}
|
||||
} else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||
// wait for MediaCodec encoder is ready to encode
|
||||
// nothing to do here because MediaCodec#dequeueInputBuffer(TIMEOUT_USEC)
|
||||
// will wait for maximum TIMEOUT_USEC(10msec) on each call
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void encode(ByteBuffer yuvBuffer,int len){
|
||||
if (!mIsCapturing) return;
|
||||
try {
|
||||
if (lastPush == 0) {
|
||||
lastPush = System.currentTimeMillis();
|
||||
}
|
||||
long time = System.currentTimeMillis() - lastPush;
|
||||
if (time >= 0) {
|
||||
time = millisPerframe - time;
|
||||
if (time > 0)
|
||||
Thread.sleep(time / 2);
|
||||
}
|
||||
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
|
||||
int bufferIndex = -1;
|
||||
try{
|
||||
bufferIndex = mMediaCodec.dequeueInputBuffer(0);
|
||||
}catch (IllegalStateException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (bufferIndex >= 0) {
|
||||
ByteBuffer mBuffer;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
mBuffer = mMediaCodec.getInputBuffer(bufferIndex);
|
||||
} else {
|
||||
mBuffer = inputBuffers[bufferIndex];
|
||||
}
|
||||
byte[] yuvData = new byte[yuvBuffer.capacity()];
|
||||
yuvBuffer.get(yuvData);
|
||||
|
||||
mBuffer.clear();
|
||||
mBuffer.put(yuvData);
|
||||
mBuffer.clear();
|
||||
mMediaCodec.queueInputBuffer(bufferIndex, 0, yuvData.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME);
|
||||
}
|
||||
lastPush = System.currentTimeMillis();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set ByteBuffer to the MediaCodec encoder
|
||||
* @param buffer null means EOS
|
||||
* @param presentationTimeUs
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void encode(final ByteBuffer buffer, final int length, final long presentationTimeUs) {
|
||||
// if (DEBUG) Log.v(TAG, "encode:buffer=" + buffer);
|
||||
if (!mIsCapturing) return;
|
||||
int ix = 0, sz;
|
||||
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
|
||||
while (mIsCapturing && ix < length) {
|
||||
final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
|
||||
if (inputBufferIndex >= 0) {
|
||||
final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
|
||||
inputBuffer.clear();
|
||||
sz = inputBuffer.remaining();
|
||||
sz = (ix + sz < length) ? sz : length - ix;
|
||||
if (sz > 0 && (buffer != null)) {
|
||||
buffer.position(ix + sz);
|
||||
buffer.flip();
|
||||
inputBuffer.put(buffer);
|
||||
}
|
||||
ix += sz;
|
||||
// if (DEBUG) Log.v(TAG, "encode:queueInputBuffer");
|
||||
if (length <= 0) {
|
||||
// send EOS
|
||||
mIsEOS = true;
|
||||
if (DEBUG) Log.i(TAG, "send BUFFER_FLAG_END_OF_STREAM");
|
||||
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0,
|
||||
presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||
break;
|
||||
} else {
|
||||
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, sz,
|
||||
presentationTimeUs, 0);
|
||||
}
|
||||
} else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||
// wait for MediaCodec encoder is ready to encode
|
||||
// nothing to do here because MediaCodec#dequeueInputBuffer(TIMEOUT_USEC)
|
||||
// will wait for maximum TIMEOUT_USEC(10msec) on each call
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer mBuffer = ByteBuffer.allocate(10240);
|
||||
|
||||
/**
|
||||
* drain encoded data and write them to muxer
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void drain() {
|
||||
if (mMediaCodec == null)
|
||||
return;
|
||||
ByteBuffer[] encoderOutputBuffers = mMediaCodec.getOutputBuffers();
|
||||
int encoderStatus, count = 0;
|
||||
final MediaMuxerWrapper muxer = mWeakMuxer.get();
|
||||
if (muxer == null) {
|
||||
Log.w(TAG, "muxer is unexpectedly null");
|
||||
return;
|
||||
}
|
||||
byte[] mPpsSps = new byte[0];
|
||||
byte[] h264 = new byte[640 * 480];
|
||||
|
||||
while (mIsCapturing) {
|
||||
encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
|
||||
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||
// 等待 TIMEOUT_USEC x 5 = 50毫秒
|
||||
// 如果还没有数据,终止循环
|
||||
if (!mIsEOS) {
|
||||
if (++count > 5)
|
||||
break;
|
||||
}
|
||||
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
||||
encoderOutputBuffers = mMediaCodec.getOutputBuffers();
|
||||
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
if (mMuxerStarted) {
|
||||
throw new RuntimeException("format changed twice");
|
||||
}
|
||||
final MediaFormat format = mMediaCodec.getOutputFormat();
|
||||
mTrackIndex = muxer.addTrack(format);
|
||||
mMuxerStarted = true;
|
||||
if (!muxer.start()) {
|
||||
synchronized (muxer) {
|
||||
while (!muxer.isStarted())
|
||||
try {
|
||||
muxer.wait(100);
|
||||
} catch (final InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (encoderStatus < 0) {
|
||||
if (DEBUG) Log.w(TAG, "drain:unexpected result from encoder#dequeueOutputBuffer: " + encoderStatus);
|
||||
} else {
|
||||
ByteBuffer encodedData;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
encodedData = mMediaCodec.getOutputBuffer(encoderStatus);
|
||||
} else {
|
||||
encodedData = encoderOutputBuffers[encoderStatus];
|
||||
}
|
||||
encodedData.position(mBufferInfo.offset);
|
||||
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
|
||||
|
||||
// final ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
|
||||
if (encodedData == null) {
|
||||
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
|
||||
}
|
||||
// BUFFER_FLAG_CODEC_CONFIG标志
|
||||
// BufferInfo清零
|
||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
||||
if (DEBUG) Log.d(TAG, "drain:BUFFER_FLAG_CODEC_CONFIG");
|
||||
mBufferInfo.size = 0;
|
||||
}
|
||||
// BUFFER_FLAG_END_OF_STREAM标志
|
||||
// 流结束,终止循环
|
||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||
mMuxerStarted = mIsCapturing = false;
|
||||
break;
|
||||
}
|
||||
// 有效编码数据流
|
||||
if (mBufferInfo.size != 0) {
|
||||
count = 0;
|
||||
if (!mMuxerStarted) {
|
||||
throw new RuntimeException("drain:muxer hasn't started");
|
||||
}
|
||||
// 写入音频流或视频流到混合器
|
||||
mBufferInfo.presentationTimeUs = getPTSUs();
|
||||
muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
|
||||
prevOutputPTSUs = mBufferInfo.presentationTimeUs;
|
||||
|
||||
// 推流,获取h.264数据流
|
||||
// mTrackIndex=0 视频;mTrackIndex=1 音频
|
||||
if(mTrackIndex == 0) {
|
||||
encodedData.position(mBufferInfo.offset);
|
||||
encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
|
||||
boolean sync = false;
|
||||
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps
|
||||
sync = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
||||
if (!sync) {
|
||||
byte[] temp = new byte[mBufferInfo.size];
|
||||
encodedData.get(temp);
|
||||
mPpsSps = temp;
|
||||
mMediaCodec.releaseOutputBuffer(encoderStatus, false);
|
||||
continue;
|
||||
} else {
|
||||
mPpsSps = new byte[0];
|
||||
}
|
||||
}
|
||||
sync |= (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
||||
int len = mPpsSps.length + mBufferInfo.size;
|
||||
if (len > h264.length) {
|
||||
h264 = new byte[len];
|
||||
}
|
||||
if (sync) {
|
||||
System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
|
||||
encodedData.get(h264, mPpsSps.length, mBufferInfo.size);
|
||||
if(mListener != null) {
|
||||
mListener.onEncodeResult(h264, 0,mPpsSps.length + mBufferInfo.size,
|
||||
mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO);
|
||||
}
|
||||
// 保存数据流到文件
|
||||
FileUtils.putFileStream(h264, 0,mPpsSps.length + mBufferInfo.size);
|
||||
} else {
|
||||
encodedData.get(h264, 0, mBufferInfo.size);
|
||||
if(mListener != null) {
|
||||
mListener.onEncodeResult(h264, 0,mBufferInfo.size,
|
||||
mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO);
|
||||
}
|
||||
FileUtils.putFileStream(h264, 0,mBufferInfo.size);
|
||||
}
|
||||
} else if(mTrackIndex == 1){
|
||||
mBuffer.clear();
|
||||
encodedData.get(mBuffer.array(), 7, mBufferInfo.size);
|
||||
encodedData.clear();
|
||||
mBuffer.position(7 + mBufferInfo.size);
|
||||
addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7);
|
||||
mBuffer.flip();
|
||||
if(mListener != null){
|
||||
mListener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7,
|
||||
mBufferInfo.presentationTimeUs / 1000,TYPE_AUDIO);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 释放输出缓存,将其还给编码器
|
||||
mMediaCodec.releaseOutputBuffer(encoderStatus, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addADTStoPacket(byte[] packet, int packetLen) {
|
||||
packet[0] = (byte) 0xFF;
|
||||
packet[1] = (byte) 0xF1;
|
||||
packet[2] = (byte) (((2 - 1) << 6) + (getSamplingRateIndex() << 2) + (1 >> 2));
|
||||
packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11));
|
||||
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
|
||||
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
|
||||
packet[6] = (byte) 0xFC;
|
||||
}
|
||||
|
||||
private int getSamplingRateIndex(){
|
||||
int mSamplingRateIndex = -1;
|
||||
for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) {
|
||||
if (AUDIO_SAMPLING_RATES[i] == MediaAudioEncoder.SAMPLE_RATE) {
|
||||
mSamplingRateIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mSamplingRateIndex;
|
||||
}
|
||||
|
||||
|
||||
private long prevOutputPTSUs = 0;
|
||||
|
||||
protected long getPTSUs() {
|
||||
long result = System.nanoTime() / 1000L;
|
||||
if (result < prevOutputPTSUs)
|
||||
result = (prevOutputPTSUs - result) + result;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package com.serenegiant.usb.encoder;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaMuxer;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MediaMuxerWrapper {
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "MediaMuxerWrapper";
|
||||
|
||||
private static final String DIR_NAME = "USBCameraTest";
|
||||
private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
|
||||
|
||||
private String mOutputPath;
|
||||
private final MediaMuxer mMediaMuxer; // API >= 18
|
||||
private int mEncoderCount, mStatredCount;
|
||||
private boolean mIsStarted;
|
||||
private MediaEncoder mVideoEncoder, mAudioEncoder;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
public MediaMuxerWrapper(String path) throws IOException {
|
||||
try {
|
||||
// 保存到自定义路径还是手机默认Movies路径
|
||||
if (TextUtils.isEmpty(path))
|
||||
mOutputPath = getCaptureFile(Environment.DIRECTORY_MOVIES, ".mp4").toString();
|
||||
mOutputPath = path;
|
||||
|
||||
} catch (final NullPointerException e) {
|
||||
throw new RuntimeException("This app has no permission of writing external storage");
|
||||
}
|
||||
mMediaMuxer = new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
||||
mEncoderCount = mStatredCount = 0;
|
||||
mIsStarted = false;
|
||||
}
|
||||
|
||||
public String getOutputPath() {
|
||||
return mOutputPath;
|
||||
}
|
||||
|
||||
public void prepare() throws IOException {
|
||||
if (mVideoEncoder != null)
|
||||
mVideoEncoder.prepare();
|
||||
if (mAudioEncoder != null)
|
||||
mAudioEncoder.prepare();
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
if (mVideoEncoder != null)
|
||||
mVideoEncoder.startRecording();
|
||||
if (mAudioEncoder != null)
|
||||
mAudioEncoder.startRecording();
|
||||
}
|
||||
|
||||
public void stopRecording() {
|
||||
if (mVideoEncoder != null)
|
||||
mVideoEncoder.stopRecording();
|
||||
mVideoEncoder = null;
|
||||
if (mAudioEncoder != null)
|
||||
mAudioEncoder.stopRecording();
|
||||
mAudioEncoder = null;
|
||||
}
|
||||
|
||||
public synchronized boolean isStarted() {
|
||||
return mIsStarted;
|
||||
}
|
||||
|
||||
//**********************************************************************
|
||||
//**********************************************************************
|
||||
/**
|
||||
* assign encoder to this calss. this is called from encoder.
|
||||
* @param encoder instance of MediaVideoEncoder or MediaAudioEncoder
|
||||
*/
|
||||
/*package*/ void addEncoder(final MediaEncoder encoder) {
|
||||
if (encoder instanceof MediaVideoEncoder) {
|
||||
if (mVideoEncoder != null)
|
||||
throw new IllegalArgumentException("Video encoder already added.");
|
||||
mVideoEncoder = encoder;
|
||||
} else if (encoder instanceof MediaSurfaceEncoder) {
|
||||
if (mVideoEncoder != null)
|
||||
throw new IllegalArgumentException("Video encoder already added.");
|
||||
mVideoEncoder = encoder;
|
||||
} else if (encoder instanceof MediaVideoBufferEncoder) {
|
||||
if (mVideoEncoder != null)
|
||||
throw new IllegalArgumentException("Video encoder already added.");
|
||||
mVideoEncoder = encoder;
|
||||
} else if (encoder instanceof MediaAudioEncoder) {
|
||||
if (mAudioEncoder != null)
|
||||
throw new IllegalArgumentException("Video encoder already added.");
|
||||
mAudioEncoder = encoder;
|
||||
} else
|
||||
throw new IllegalArgumentException("unsupported encoder");
|
||||
mEncoderCount = (mVideoEncoder != null ? 1 : 0) + (mAudioEncoder != null ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* request start recording from encoder
|
||||
* @return true when muxer is ready to write
|
||||
*/
|
||||
/*package*/ synchronized boolean start() {
|
||||
if (DEBUG) Log.v(TAG, "start:");
|
||||
mStatredCount++;
|
||||
if ((mEncoderCount > 0) && (mStatredCount == mEncoderCount)) {
|
||||
mMediaMuxer.start();
|
||||
mIsStarted = true;
|
||||
notifyAll();
|
||||
if (DEBUG) Log.v(TAG, "MediaMuxer started:");
|
||||
}
|
||||
return mIsStarted;
|
||||
}
|
||||
|
||||
/**
|
||||
* request stop recording from encoder when encoder received EOS
|
||||
*/
|
||||
/*package*/ synchronized void stop() {
|
||||
if (DEBUG) Log.v(TAG, "stop:mStatredCount=" + mStatredCount);
|
||||
mStatredCount--;
|
||||
if ((mEncoderCount > 0) && (mStatredCount <= 0)) {
|
||||
try {
|
||||
mMediaMuxer.stop();
|
||||
} catch (final Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
mIsStarted = false;
|
||||
if (DEBUG) Log.v(TAG, "MediaMuxer stopped:");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* assign encoder to muxer
|
||||
* @param format
|
||||
* @return minus value indicate error
|
||||
*/
|
||||
/*package*/ synchronized int addTrack(final MediaFormat format) {
|
||||
if (mIsStarted)
|
||||
throw new IllegalStateException("muxer already started");
|
||||
final int trackIx = mMediaMuxer.addTrack(format);
|
||||
if (DEBUG) Log.i(TAG, "addTrack:trackNum=" + mEncoderCount + ",trackIx=" + trackIx + ",format=" + format);
|
||||
return trackIx;
|
||||
}
|
||||
|
||||
/**
|
||||
* write encoded data to muxer
|
||||
* @param trackIndex
|
||||
* @param byteBuf
|
||||
* @param bufferInfo
|
||||
*/
|
||||
/*package*/ synchronized void writeSampleData(final int trackIndex, final ByteBuffer byteBuf, final MediaCodec.BufferInfo bufferInfo) {
|
||||
if (mStatredCount > 0)
|
||||
mMediaMuxer.writeSampleData(trackIndex, byteBuf, bufferInfo);
|
||||
}
|
||||
|
||||
//**********************************************************************
|
||||
//**********************************************************************
|
||||
/**
|
||||
* generate output file
|
||||
* @param type Environment.DIRECTORY_MOVIES / Environment.DIRECTORY_DCIM etc.
|
||||
* @param ext .mp4(.m4a for audio) or .png
|
||||
* @return return null when this app has no writing permission to external storage.
|
||||
*/
|
||||
public static final File getCaptureFile(final String type, final String ext) {
|
||||
final File dir = new File(Environment.getExternalStoragePublicDirectory(type), DIR_NAME);
|
||||
Log.d(TAG, "path=" + dir.toString());
|
||||
dir.mkdirs();
|
||||
if (dir.canWrite()) {
|
||||
return new File(dir, getDateTimeString() + ext);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current date and time as String
|
||||
* @return
|
||||
*/
|
||||
private static final String getDateTimeString() {
|
||||
final GregorianCalendar now = new GregorianCalendar();
|
||||
return mDateTimeFormat.format(now.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MediaSurfaceEncoder extends MediaEncoder implements IVideoEncoder {
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "MediaSurfaceEncoder";
|
||||
|
||||
private static final String MIME_TYPE = "video/avc";
|
||||
// parameters for recording
|
||||
private final int mWidth, mHeight;
|
||||
private static final int FRAME_RATE = 15;
|
||||
private static final float BPP = 0.50f;
|
||||
|
||||
private Surface mSurface;
|
||||
|
||||
public MediaSurfaceEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) {
|
||||
super(muxer, listener);
|
||||
if (DEBUG) Log.i(TAG, "MediaVideoEncoder: ");
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoder's input surface.
|
||||
*/
|
||||
public Surface getInputSurface() {
|
||||
return mSurface;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepare() throws IOException {
|
||||
if (DEBUG) Log.i(TAG, "prepare: ");
|
||||
mTrackIndex = -1;
|
||||
mMuxerStarted = mIsEOS = false;
|
||||
|
||||
final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE);
|
||||
if (videoCodecInfo == null) {
|
||||
Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName());
|
||||
|
||||
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
|
||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());
|
||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
|
||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
|
||||
if (DEBUG) Log.i(TAG, "format: " + format);
|
||||
|
||||
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
|
||||
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
// get Surface for encoder input
|
||||
// this method only can call between #configure and #start
|
||||
mSurface = mMediaCodec.createInputSurface(); // API >= 18
|
||||
mMediaCodec.start();
|
||||
if (DEBUG) Log.i(TAG, "prepare finishing");
|
||||
if (mListener != null) {
|
||||
try {
|
||||
mListener.onPrepared(this);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "prepare:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void release() {
|
||||
if (DEBUG) Log.i(TAG, "release:");
|
||||
if (mSurface != null) {
|
||||
mSurface.release();
|
||||
mSurface = null;
|
||||
}
|
||||
super.release();
|
||||
}
|
||||
|
||||
private int calcBitRate() {
|
||||
final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight);
|
||||
Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* select the first codec that match a specific MIME type
|
||||
* @param mimeType
|
||||
* @return null if no codec matched
|
||||
*/
|
||||
protected static final MediaCodecInfo selectVideoCodec(final String mimeType) {
|
||||
if (DEBUG) Log.v(TAG, "selectVideoCodec:");
|
||||
|
||||
// get the list of available codecs
|
||||
final int numCodecs = MediaCodecList.getCodecCount();
|
||||
for (int i = 0; i < numCodecs; i++) {
|
||||
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
if (!codecInfo.isEncoder()) { // skipp decoder
|
||||
continue;
|
||||
}
|
||||
// select first codec that match a specific MIME type and color format
|
||||
final String[] types = codecInfo.getSupportedTypes();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
if (types[j].equalsIgnoreCase(mimeType)) {
|
||||
if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]);
|
||||
final int format = selectColorFormat(codecInfo, mimeType);
|
||||
if (format > 0) {
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* select color format available on specific codec and we can use.
|
||||
* @return 0 if no colorFormat is matched
|
||||
*/
|
||||
protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) {
|
||||
if (DEBUG) Log.i(TAG, "selectColorFormat: ");
|
||||
int result = 0;
|
||||
final MediaCodecInfo.CodecCapabilities caps;
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
caps = codecInfo.getCapabilitiesForType(mimeType);
|
||||
} finally {
|
||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
int colorFormat;
|
||||
for (int i = 0; i < caps.colorFormats.length; i++) {
|
||||
colorFormat = caps.colorFormats[i];
|
||||
if (isRecognizedVideoFormat(colorFormat)) {
|
||||
if (result == 0)
|
||||
result = colorFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == 0)
|
||||
Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* color formats that we can use in this class
|
||||
*/
|
||||
protected static int[] recognizedFormats;
|
||||
static {
|
||||
recognizedFormats = new int[] {
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
|
||||
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface,
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean isRecognizedVideoFormat(final int colorFormat) {
|
||||
if (DEBUG) Log.i(TAG, "isRecognizedVideoFormat:colorFormat=" + colorFormat);
|
||||
final int n = recognizedFormats != null ? recognizedFormats.length : 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (recognizedFormats[i] == colorFormat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class MediaVideoBufferEncoder extends MediaEncoder implements IVideoEncoder {
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "MediaVideoBufferEncoder";
|
||||
|
||||
private static final String MIME_TYPE = "video/avc";
|
||||
private static final int FRAME_RATE = 15;
|
||||
private static final float BPP = 0.50f;
|
||||
|
||||
private final int mWidth, mHeight;
|
||||
protected int mColorFormat;
|
||||
|
||||
public MediaVideoBufferEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) {
|
||||
super(muxer, listener);
|
||||
if (DEBUG) Log.i(TAG, "MediaVideoEncoder: ");
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
public void encode(final ByteBuffer buffer) {
|
||||
synchronized (mSync) {
|
||||
if (!mIsCapturing || mRequestStop) return;
|
||||
}
|
||||
// encode(buffer, buffer.capacity(), getPTSUs());
|
||||
encode(buffer, buffer.capacity());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepare() throws IOException {
|
||||
if (DEBUG) Log.i(TAG, "prepare: ");
|
||||
mTrackIndex = -1;
|
||||
mMuxerStarted = mIsEOS = false;
|
||||
|
||||
final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE);
|
||||
if (videoCodecInfo == null) {
|
||||
Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName());
|
||||
|
||||
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
|
||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());
|
||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
|
||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
|
||||
if (DEBUG) Log.i(TAG, "format: " + format);
|
||||
|
||||
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
|
||||
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
mMediaCodec.start();
|
||||
|
||||
Bundle params = new Bundle();
|
||||
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
mMediaCodec.setParameters(params);
|
||||
}
|
||||
if (DEBUG) Log.i(TAG, "prepare finishing");
|
||||
if (mListener != null) {
|
||||
try {
|
||||
mListener.onPrepared(this);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "prepare:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calcBitRate() {
|
||||
final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight);
|
||||
Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
// 选择第一个与制定MIME类型匹配的编码器
|
||||
@SuppressWarnings("deprecation")
|
||||
protected final MediaCodecInfo selectVideoCodec(final String mimeType) {
|
||||
if (DEBUG) Log.v(TAG, "selectVideoCodec:");
|
||||
|
||||
// get the list of available codecs
|
||||
final int numCodecs = MediaCodecList.getCodecCount();
|
||||
for (int i = 0; i < numCodecs; i++) {
|
||||
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
if (!codecInfo.isEncoder()) { // skipp decoder
|
||||
continue;
|
||||
}
|
||||
// select first codec that match a specific MIME type and color format
|
||||
final String[] types = codecInfo.getSupportedTypes();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
if (types[j].equalsIgnoreCase(mimeType)) {
|
||||
if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]);
|
||||
final int format = selectColorFormat(codecInfo, mimeType);
|
||||
if (format > 0) {
|
||||
mColorFormat = format;
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 选择编码器支持的格式
|
||||
protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) {
|
||||
if (DEBUG) Log.i(TAG, "selectColorFormat: ");
|
||||
int result = 0;
|
||||
final MediaCodecInfo.CodecCapabilities caps;
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
caps = codecInfo.getCapabilitiesForType(mimeType);
|
||||
} finally {
|
||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
int colorFormat;
|
||||
for (int i = 0; i < caps.colorFormats.length; i++) {
|
||||
colorFormat = caps.colorFormats[i];
|
||||
if (isRecognizedViewoFormat(colorFormat)) {
|
||||
if (result == 0)
|
||||
result = colorFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == 0)
|
||||
Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
|
||||
return result;
|
||||
}
|
||||
|
||||
// YUV颜色格式
|
||||
protected static int[] recognizedFormats;
|
||||
static {
|
||||
recognizedFormats = new int[] {
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
|
||||
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
|
||||
MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface,
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean isRecognizedViewoFormat(final int colorFormat) {
|
||||
if (DEBUG) Log.i(TAG, "isRecognizedViewoFormat:colorFormat=" + colorFormat);
|
||||
final int n = recognizedFormats != null ? recognizedFormats.length : 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (recognizedFormats[i] == colorFormat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.TargetApi;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.serenegiant.glutils.EGLBase;
|
||||
import com.serenegiant.glutils.RenderHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Encode texture images as H.264 video
|
||||
* using MediaCodec.
|
||||
* This class render texture images into recording surface
|
||||
* camera from MediaCodec encoder using Open GL|ES
|
||||
*/
|
||||
public class MediaVideoEncoder extends MediaEncoder implements IVideoEncoder {
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "MediaVideoEncoder";
|
||||
|
||||
private static final String MIME_TYPE = "video/avc";
|
||||
// parameters for recording
|
||||
private final int mWidth, mHeight;
|
||||
private static final int FRAME_RATE = 15;
|
||||
private static final float BPP = 0.50f;
|
||||
|
||||
private RenderHandler mRenderHandler;
|
||||
private Surface mSurface;
|
||||
|
||||
public MediaVideoEncoder(final MediaMuxerWrapper muxer, final int width, final int height, final MediaEncoderListener listener) {
|
||||
super(muxer, listener);
|
||||
if (DEBUG) Log.i(TAG, "MediaVideoEncoder: ");
|
||||
mRenderHandler = RenderHandler.createHandler(TAG);
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
public boolean frameAvailableSoon(final float[] tex_matrix) {
|
||||
boolean result;
|
||||
if (result = super.frameAvailableSoon())
|
||||
mRenderHandler.draw(tex_matrix);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does not work correctly on this class,
|
||||
* use #frameAvailableSoon(final float[]) instead
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean frameAvailableSoon() {
|
||||
boolean result;
|
||||
if (result = super.frameAvailableSoon())
|
||||
mRenderHandler.draw(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@Override
|
||||
protected void prepare() throws IOException {
|
||||
if (DEBUG) Log.i(TAG, "prepare: ");
|
||||
mTrackIndex = -1;
|
||||
mMuxerStarted = mIsEOS = false;
|
||||
|
||||
final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE);
|
||||
if (videoCodecInfo == null) {
|
||||
Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Log.i(TAG, "selected codec: " + videoCodecInfo.getName());
|
||||
|
||||
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
|
||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // API >= 18
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());
|
||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
|
||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
||||
if (DEBUG) Log.i(TAG, "format: " + format);
|
||||
|
||||
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
|
||||
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
// get Surface for encoder input
|
||||
// this method only can call between #configure and #start
|
||||
mSurface = mMediaCodec.createInputSurface(); // API >= 18
|
||||
mMediaCodec.start();
|
||||
if (DEBUG) Log.i(TAG, "prepare finishing");
|
||||
if (mListener != null) {
|
||||
try {
|
||||
mListener.onPrepared(this);
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "prepare:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEglContext(final EGLBase.IContext sharedContext, final int tex_id) {
|
||||
mRenderHandler.setEglContext(sharedContext, tex_id, mSurface, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void release() {
|
||||
if (DEBUG) Log.i(TAG, "release:");
|
||||
if (mSurface != null) {
|
||||
mSurface.release();
|
||||
mSurface = null;
|
||||
}
|
||||
if (mRenderHandler != null) {
|
||||
mRenderHandler.release();
|
||||
mRenderHandler = null;
|
||||
}
|
||||
super.release();
|
||||
}
|
||||
|
||||
private int calcBitRate() {
|
||||
final int bitrate = (int)(BPP * FRAME_RATE * mWidth * mHeight);
|
||||
Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* select the first codec that match a specific MIME type
|
||||
* @param mimeType
|
||||
* @return null if no codec matched
|
||||
*/
|
||||
protected static final MediaCodecInfo selectVideoCodec(final String mimeType) {
|
||||
if (DEBUG) Log.v(TAG, "selectVideoCodec:");
|
||||
|
||||
// get the list of available codecs
|
||||
final int numCodecs = MediaCodecList.getCodecCount();
|
||||
for (int i = 0; i < numCodecs; i++) {
|
||||
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
if (!codecInfo.isEncoder()) { // skipp decoder
|
||||
continue;
|
||||
}
|
||||
// select first codec that match a specific MIME type and color format
|
||||
final String[] types = codecInfo.getSupportedTypes();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
if (types[j].equalsIgnoreCase(mimeType)) {
|
||||
if (DEBUG) Log.i(TAG, "codec:" + codecInfo.getName() + ",MIME=" + types[j]);
|
||||
final int format = selectColorFormat(codecInfo, mimeType);
|
||||
if (format > 0) {
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* select color format available on specific codec and we can use.
|
||||
* @return 0 if no colorFormat is matched
|
||||
*/
|
||||
protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) {
|
||||
if (DEBUG) Log.i(TAG, "selectColorFormat: ");
|
||||
int result = 0;
|
||||
final MediaCodecInfo.CodecCapabilities caps;
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
caps = codecInfo.getCapabilitiesForType(mimeType);
|
||||
} finally {
|
||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
int colorFormat;
|
||||
for (int i = 0; i < caps.colorFormats.length; i++) {
|
||||
colorFormat = caps.colorFormats[i];
|
||||
if (isRecognizedVideoFormat(colorFormat)) {
|
||||
if (result == 0)
|
||||
result = colorFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == 0)
|
||||
Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* color formats that we can use in this class
|
||||
*/
|
||||
protected static int[] recognizedFormats;
|
||||
static {
|
||||
recognizedFormats = new int[] {
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
|
||||
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface,
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean isRecognizedVideoFormat(final int colorFormat) {
|
||||
if (DEBUG) Log.i(TAG, "isRecognizedVideoFormat:colorFormat=" + colorFormat);
|
||||
final int n = recognizedFormats != null ? recognizedFormats.length : 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (recognizedFormats[i] == colorFormat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.serenegiant.usb.encoder;
|
||||
|
||||
/** 录制参数
|
||||
*
|
||||
* Created by jiangdongguo on 2017/10/19.
|
||||
*/
|
||||
|
||||
public class RecordParams {
|
||||
private String recordPath;
|
||||
private int recordDuration;
|
||||
private boolean voiceClose;
|
||||
private boolean isAutoSave;
|
||||
private boolean isSupportOverlay;
|
||||
|
||||
public RecordParams() {
|
||||
}
|
||||
|
||||
public boolean isSupportOverlay() {
|
||||
return isSupportOverlay;
|
||||
}
|
||||
|
||||
public void setSupportOverlay(boolean supportOverlay) {
|
||||
isSupportOverlay = supportOverlay;
|
||||
}
|
||||
|
||||
public boolean isVoiceClose() {
|
||||
return voiceClose;
|
||||
}
|
||||
|
||||
public void setVoiceClose(boolean voiceClose) {
|
||||
this.voiceClose = voiceClose;
|
||||
}
|
||||
|
||||
public String getRecordPath() {
|
||||
return recordPath;
|
||||
}
|
||||
|
||||
public void setRecordPath(String recordPath) {
|
||||
this.recordPath = recordPath;
|
||||
}
|
||||
|
||||
public int getRecordDuration() {
|
||||
return recordDuration;
|
||||
}
|
||||
|
||||
public void setRecordDuration(int recordDuration) {
|
||||
this.recordDuration = recordDuration;
|
||||
}
|
||||
|
||||
public boolean isAutoSave() {
|
||||
return isAutoSave;
|
||||
}
|
||||
|
||||
public void setAutoSave(boolean autoSave) {
|
||||
isAutoSave = autoSave;
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
package com.serenegiant.usb.encoder.biz;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
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;
|
||||
|
||||
/**将PCM编码为AAC
|
||||
*
|
||||
* Created by jianddongguo on 2017/7/21.
|
||||
*/
|
||||
|
||||
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";
|
||||
private static final long TIMES_OUT = 1000;
|
||||
private static final int SAMPLE_RATE = 8000; // 采样率
|
||||
private static final int BIT_RATE = 16000; // 比特率
|
||||
private static final int BUFFER_SIZE = 1920; // 最小缓存
|
||||
private int outChannel = 1;
|
||||
private int bitRateForLame = 32;
|
||||
private int qaulityDegree = 7;
|
||||
private int bufferSizeInBytes;
|
||||
|
||||
private AudioRecord mAudioRecord; // 音频采集
|
||||
private MediaCodec mAudioEncoder; // 音频编码
|
||||
private OnAACEncodeResultListener listener;
|
||||
private int mSamplingRateIndex = 0;//ADTS
|
||||
private boolean isEncoderStart = false;
|
||||
private boolean isRecMp3 = false;
|
||||
private boolean isExit = false;
|
||||
private long prevPresentationTimes = 0;
|
||||
private WeakReference<Mp4MediaMuxer> mMuxerRef;
|
||||
private MediaFormat newFormat;
|
||||
|
||||
private static final int[] AUDIO_SOURCES = new int[] {
|
||||
MediaRecorder.AudioSource.DEFAULT,
|
||||
MediaRecorder.AudioSource.MIC,
|
||||
MediaRecorder.AudioSource.CAMCORDER,
|
||||
};
|
||||
/**
|
||||
* There are 13 supported frequencies by ADTS.
|
||||
**/
|
||||
public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0
|
||||
88200, // 1
|
||||
64000, // 2
|
||||
48000, // 3
|
||||
44100, // 4
|
||||
32000, // 5
|
||||
24000, // 6
|
||||
22050, // 7
|
||||
16000, // 8
|
||||
12000, // 9
|
||||
11025, // 10
|
||||
8000, // 11
|
||||
7350, // 12
|
||||
-1, // 13
|
||||
-1, // 14
|
||||
-1, // 15
|
||||
};
|
||||
|
||||
private FileOutputStream fops;
|
||||
|
||||
// 编码流结果回调接口
|
||||
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++) {
|
||||
if (AUDIO_SAMPLING_RATES[i] == SAMPLE_RATE) {
|
||||
mSamplingRateIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnAACEncodeResultListener(OnAACEncodeResultListener listener){
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void exit(){
|
||||
isExit = true;
|
||||
}
|
||||
|
||||
public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer){
|
||||
this.mMuxerRef = new WeakReference<>(mMuxer);
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null && newFormat != null) {
|
||||
muxer.addTrack(newFormat, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// 开启音频采集、编码
|
||||
if(! isEncoderStart){
|
||||
initAudioRecord();
|
||||
initMediaCodec();
|
||||
}
|
||||
// 初始化音频文件参数
|
||||
byte[] mp3Buffer = new byte[1024];
|
||||
|
||||
// 这里有问题,当本地录制结束后,没有写入
|
||||
while(! isExit){
|
||||
byte[] audioBuffer = new byte[2048];
|
||||
// 采集音频
|
||||
int readBytes = mAudioRecord.read(audioBuffer,0,BUFFER_SIZE);
|
||||
|
||||
if(DEBUG)
|
||||
Log.i(TAG,"采集音频readBytes = "+readBytes);
|
||||
// 编码音频
|
||||
if(readBytes > 0){
|
||||
encodeBytes(audioBuffer,readBytes);
|
||||
}
|
||||
}
|
||||
// 停止音频采集、编码
|
||||
stopMediaCodec();
|
||||
stopAudioRecord();
|
||||
}
|
||||
|
||||
|
||||
@TargetApi(21)
|
||||
private void encodeBytes(byte[] audioBuf, int readBytes) {
|
||||
ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();
|
||||
ByteBuffer[] outputBuffers = mAudioEncoder.getOutputBuffers();
|
||||
//返回编码器的一个输入缓存区句柄,-1表示当前没有可用的输入缓存区
|
||||
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(TIMES_OUT);
|
||||
if(inputBufferIndex >= 0){
|
||||
// 绑定一个被空的、可写的输入缓存区inputBuffer到客户端
|
||||
ByteBuffer inputBuffer = null;
|
||||
if(!isLollipop()){
|
||||
inputBuffer = inputBuffers[inputBufferIndex];
|
||||
}else{
|
||||
inputBuffer = mAudioEncoder.getInputBuffer(inputBufferIndex);
|
||||
}
|
||||
// 向输入缓存区写入有效原始数据,并提交到编码器中进行编码处理
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回一个输出缓存区句柄,当为-1时表示当前没有可用的输出缓存区
|
||||
// mBufferInfo参数包含被编码好的数据,timesOut参数为超时等待的时间
|
||||
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){
|
||||
// 如果API小于21,APP需要重新绑定编码器的输入缓存区;
|
||||
// 如果API大于21,则无需处理INFO_OUTPUT_BUFFERS_CHANGED
|
||||
if(!isLollipop()){
|
||||
outputBuffers = mAudioEncoder.getOutputBuffers();
|
||||
}
|
||||
}else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
|
||||
// 编码器输出缓存区格式改变,通常在存储数据之前且只会改变一次
|
||||
// 这里设置混合器视频轨道,如果音频已经添加则启动混合器(保证音视频同步)
|
||||
if(DEBUG)
|
||||
Log.i(TAG,"编码器输出缓存区格式改变,添加视频轨道到混合器");
|
||||
synchronized (AACEncodeConsumer.this) {
|
||||
newFormat = mAudioEncoder.getOutputFormat();
|
||||
if(mMuxerRef != null){
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null) {
|
||||
muxer.addTrack(newFormat, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// 当flag属性置为BUFFER_FLAG_CODEC_CONFIG后,说明输出缓存区的数据已经被消费了
|
||||
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,"数据流结束,退出循环");
|
||||
break;
|
||||
}
|
||||
// 获取一个只读的输出缓存区inputBuffer ,它包含被编码好的数据
|
||||
ByteBuffer mBuffer = ByteBuffer.allocate(10240);
|
||||
ByteBuffer outputBuffer = null;
|
||||
if(!isLollipop()){
|
||||
outputBuffer = outputBuffers[outputBufferIndex];
|
||||
}else{
|
||||
outputBuffer = mAudioEncoder.getOutputBuffer(outputBufferIndex);
|
||||
}
|
||||
if(mBufferInfo.size != 0){
|
||||
// 获取输出缓存区失败,抛出异常
|
||||
if(outputBuffer == null){
|
||||
throw new RuntimeException("encodecOutputBuffer"+outputBufferIndex+"was null");
|
||||
}
|
||||
// 添加视频流到混合器
|
||||
if(mMuxerRef != null){
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null) {
|
||||
muxer.pumpStream(outputBuffer, mBufferInfo, false);
|
||||
}
|
||||
}
|
||||
// AAC流添加ADTS头,缓存到mBuffer
|
||||
mBuffer.clear();
|
||||
outputBuffer.get(mBuffer.array(), 7, mBufferInfo.size);
|
||||
outputBuffer.clear();
|
||||
mBuffer.position(7 + mBufferInfo.size);
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 处理结束,释放输出缓存区资源
|
||||
mAudioEncoder.releaseOutputBuffer(outputBufferIndex,false);
|
||||
}
|
||||
}while (outputBufferIndex >= 0);
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
mAudioRecord = new AudioRecord(src,
|
||||
SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
|
||||
if (mAudioRecord != null) {
|
||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
mAudioRecord = null;
|
||||
}
|
||||
if (mAudioRecord != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
private void initMediaCodec(){
|
||||
if(DEBUG)
|
||||
Log.d(TAG,"AACEncodeConsumer-->开始编码音频");
|
||||
MediaCodecInfo mCodecInfo = selectSupportCodec(MIME_TYPE);
|
||||
if(mCodecInfo == null){
|
||||
Log.e(TAG,"编码器不支持"+MIME_TYPE+"类型");
|
||||
return;
|
||||
}
|
||||
try{
|
||||
mAudioEncoder = MediaCodec.createByCodecName(mCodecInfo.getName());
|
||||
}catch(IOException e){
|
||||
Log.e(TAG,"创建编码器失败"+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
MediaFormat format = new MediaFormat();
|
||||
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
|
||||
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
|
||||
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
|
||||
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
||||
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, BUFFER_SIZE);
|
||||
mAudioEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
mAudioEncoder.start();
|
||||
isEncoderStart = true;
|
||||
}
|
||||
|
||||
private void stopAudioRecord() {
|
||||
if(DEBUG)
|
||||
Log.d(TAG,"AACEncodeConsumer-->停止采集音频");
|
||||
if(mAudioRecord != null){
|
||||
mAudioRecord.stop();
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopMediaCodec() {
|
||||
if(DEBUG)
|
||||
Log.d(TAG,"AACEncodeConsumer-->停止编码音频");
|
||||
if(mAudioEncoder != null){
|
||||
mAudioEncoder.stop();
|
||||
mAudioEncoder.release();
|
||||
mAudioEncoder = null;
|
||||
}
|
||||
isEncoderStart = false;
|
||||
}
|
||||
|
||||
// API>=21
|
||||
private boolean isLollipop(){
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||
}
|
||||
|
||||
// API<=19
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历所有编解码器,返回第一个与指定MIME类型匹配的编码器
|
||||
* 判断是否有支持指定mime类型的编码器
|
||||
* */
|
||||
private MediaCodecInfo selectSupportCodec(String mimeType){
|
||||
int numCodecs = MediaCodecList.getCodecCount();
|
||||
for (int i = 0; i < numCodecs; i++) {
|
||||
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
// 判断是否为编码器,否则直接进入下一次循环
|
||||
if (!codecInfo.isEncoder()) {
|
||||
continue;
|
||||
}
|
||||
// 如果是编码器,判断是否支持Mime类型
|
||||
String[] types = codecInfo.getSupportedTypes();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
if (types[j].equalsIgnoreCase(mimeType)) {
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addADTStoPacket(byte[] packet, int packetLen) {
|
||||
packet[0] = (byte) 0xFF;
|
||||
packet[1] = (byte) 0xF1;
|
||||
packet[2] = (byte) (((2 - 1) << 6) + (mSamplingRateIndex << 2) + (1 >> 2));
|
||||
packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11));
|
||||
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
|
||||
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
|
||||
packet[6] = (byte) 0xFC;
|
||||
}
|
||||
|
||||
|
||||
private short[] transferByte2Short(byte[] data,int readBytes){
|
||||
// byte[] 转 short[],数组长度缩减一半
|
||||
int shortLen = readBytes / 2;
|
||||
// 将byte[]数组装如ByteBuffer缓冲区
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(data, 0, readBytes);
|
||||
// 将ByteBuffer转成小端并获取shortBuffer
|
||||
ShortBuffer shortBuffer = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||
short[] shortData = new short[shortLen];
|
||||
shortBuffer.get(shortData, 0, shortLen);
|
||||
return shortData;
|
||||
}
|
||||
}
|
||||
@@ -1,461 +0,0 @@
|
||||
package com.serenegiant.usb.encoder.biz;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
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;
|
||||
private static final String TAG = "H264EncodeConsumer";
|
||||
private static final String MIME_TYPE = "video/avc";
|
||||
// 间隔1s插入一帧关键帧
|
||||
private static final int FRAME_INTERVAL = 1;
|
||||
// 绑定编码器缓存区超时时间为10s
|
||||
private static final int TIMES_OUT = 10000;
|
||||
// 硬编码器
|
||||
private MediaCodec mMediaCodec;
|
||||
private int mColorFormat;
|
||||
private boolean isExit = false;
|
||||
private boolean isEncoderStart = false;
|
||||
|
||||
private MediaFormat mFormat;
|
||||
private static String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test2.h264";
|
||||
private BufferedOutputStream outputStream;
|
||||
final int millisPerframe = 1000 / 20;
|
||||
long lastPush = 0;
|
||||
private OnH264EncodeResultListener listener;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private MediaFormat newFormat;
|
||||
private WeakReference<Mp4MediaMuxer> mMuxerRef;
|
||||
private boolean isAddKeyFrame = false;
|
||||
|
||||
public interface OnH264EncodeResultListener {
|
||||
void onEncodeResult(byte[] data, int offset,
|
||||
int length, long timestamp);
|
||||
}
|
||||
|
||||
public void setOnH264EncodeResultListener(OnH264EncodeResultListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public H264EncodeConsumer(int width, int height) {
|
||||
this.mWidth = width;
|
||||
this.mHeight = height;
|
||||
}
|
||||
|
||||
public synchronized void setTmpuMuxer(Mp4MediaMuxer mMuxer) {
|
||||
this.mMuxerRef = new WeakReference<>(mMuxer);
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null && newFormat != null) {
|
||||
muxer.addTrack(newFormat, true);
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBuffer[] inputBuffers;
|
||||
private ByteBuffer[] outputBuffers;
|
||||
|
||||
public void setRawYuv(byte[] yuvData, int width, int height) {
|
||||
if (!isEncoderStart)
|
||||
return;
|
||||
if (mWidth != width || mHeight != height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (lastPush == 0) {
|
||||
lastPush = System.currentTimeMillis();
|
||||
}
|
||||
long time = System.currentTimeMillis() - lastPush;
|
||||
if (time >= 0) {
|
||||
time = millisPerframe - time;
|
||||
if (time > 0)
|
||||
Thread.sleep(time / 2);
|
||||
}
|
||||
// 将数据写入编码器
|
||||
|
||||
feedMediaCodecData(nv12ToNV21(yuvData, mWidth, mHeight));
|
||||
|
||||
if (time > 0)
|
||||
Thread.sleep(time / 2);
|
||||
lastPush = System.currentTimeMillis();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void feedMediaCodecData(byte[] data) {
|
||||
if (!isEncoderStart)
|
||||
return;
|
||||
int bufferIndex = -1;
|
||||
try {
|
||||
bufferIndex = mMediaCodec.dequeueInputBuffer(0);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (bufferIndex >= 0) {
|
||||
ByteBuffer buffer;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
buffer = mMediaCodec.getInputBuffer(bufferIndex);
|
||||
} else {
|
||||
buffer = inputBuffers[bufferIndex];
|
||||
}
|
||||
buffer.clear();
|
||||
buffer.put(data);
|
||||
buffer.clear();
|
||||
mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME);
|
||||
}
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
isExit = true;
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isEncoderStart) {
|
||||
startMediaCodec();
|
||||
}
|
||||
// 休眠200ms,等待音频线程开启
|
||||
// 否则视频第一秒会卡住
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
// 如果编码器没有启动或者没有图像数据,线程阻塞等待
|
||||
while (!isExit) {
|
||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
int outputBufferIndex = 0;
|
||||
byte[] mPpsSps = new byte[0];
|
||||
byte[] h264 = new byte[mWidth * mHeight];
|
||||
do {
|
||||
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
|
||||
if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||
// no output available yet
|
||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
||||
// not expected for an encoder
|
||||
outputBuffers = mMediaCodec.getOutputBuffers();
|
||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
synchronized (H264EncodeConsumer.this) {
|
||||
newFormat = mMediaCodec.getOutputFormat();
|
||||
if (mMuxerRef != null) {
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null) {
|
||||
muxer.addTrack(newFormat, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (outputBufferIndex < 0) {
|
||||
// let's ignore it
|
||||
} else {
|
||||
ByteBuffer outputBuffer;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
|
||||
} else {
|
||||
outputBuffer = outputBuffers[outputBufferIndex];
|
||||
}
|
||||
outputBuffer.position(bufferInfo.offset);
|
||||
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
|
||||
|
||||
boolean sync = false;
|
||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps
|
||||
sync = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
||||
if (!sync) {
|
||||
byte[] temp = new byte[bufferInfo.size];
|
||||
outputBuffer.get(temp);
|
||||
mPpsSps = temp;
|
||||
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
|
||||
continue;
|
||||
} else {
|
||||
mPpsSps = new byte[0];
|
||||
}
|
||||
}
|
||||
sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
||||
int len = mPpsSps.length + bufferInfo.size;
|
||||
if (len > h264.length) {
|
||||
h264 = new byte[len];
|
||||
}
|
||||
if (sync) {
|
||||
System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
|
||||
outputBuffer.get(h264, mPpsSps.length, bufferInfo.size);
|
||||
if (listener != null) {
|
||||
listener.onEncodeResult(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000);
|
||||
}
|
||||
|
||||
// 添加视频流到混合器
|
||||
if (mMuxerRef != null) {
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null) {
|
||||
muxer.pumpStream(outputBuffer, bufferInfo, true);
|
||||
}
|
||||
isAddKeyFrame = true;
|
||||
}
|
||||
if (DEBUG)
|
||||
Log.i(TAG, "关键帧 h264.length = " + h264.length + ";mPpsSps.length=" + mPpsSps.length
|
||||
+ " bufferInfo.size = " + bufferInfo.size);
|
||||
} else {
|
||||
outputBuffer.get(h264, 0, bufferInfo.size);
|
||||
if (listener != null) {
|
||||
listener.onEncodeResult(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000);
|
||||
}
|
||||
// 添加视频流到混合器
|
||||
if (isAddKeyFrame && mMuxerRef != null) {
|
||||
Mp4MediaMuxer muxer = mMuxerRef.get();
|
||||
if (muxer != null) {
|
||||
muxer.pumpStream(outputBuffer, bufferInfo, true);
|
||||
}
|
||||
}
|
||||
if (DEBUG)
|
||||
Log.i(TAG, "普通帧 h264.length = " + h264.length + " bufferInfo.size = " + bufferInfo.size);
|
||||
}
|
||||
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
|
||||
}
|
||||
} while (!isExit && isEncoderStart);
|
||||
}
|
||||
stopMediaCodec();
|
||||
}
|
||||
|
||||
private void startMediaCodec() {
|
||||
final MediaCodecInfo videoCodecInfo = selectVideoCodec(MIME_TYPE);
|
||||
if (videoCodecInfo == null) {
|
||||
Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
|
||||
return;
|
||||
}
|
||||
|
||||
final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
|
||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);
|
||||
format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());
|
||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
|
||||
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
||||
|
||||
try {
|
||||
mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
mMediaCodec.start();
|
||||
|
||||
|
||||
isEncoderStart = true;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + 1) {
|
||||
inputBuffers = outputBuffers = null;
|
||||
} else {
|
||||
inputBuffers = mMediaCodec.getInputBuffers();
|
||||
outputBuffers = mMediaCodec.getOutputBuffers();
|
||||
}
|
||||
|
||||
Bundle params = new Bundle();
|
||||
params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
mMediaCodec.setParameters(params);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopMediaCodec() {
|
||||
isEncoderStart = false;
|
||||
if (mMediaCodec != null) {
|
||||
mMediaCodec.stop();
|
||||
mMediaCodec.release();
|
||||
Log.d(TAG, "关闭视频编码器");
|
||||
}
|
||||
}
|
||||
|
||||
private static final int FRAME_RATE = 15;
|
||||
private static final float BPP = 0.50f;
|
||||
|
||||
private int calcBitRate() {
|
||||
final int bitrate = (int) (BPP * FRAME_RATE * mWidth * mHeight);
|
||||
Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* select the first codec that match a specific MIME type
|
||||
*
|
||||
* @param mimeType
|
||||
* @return null if no codec matched
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected final MediaCodecInfo selectVideoCodec(final String mimeType) {
|
||||
|
||||
// get the list of available codecs
|
||||
final int numCodecs = MediaCodecList.getCodecCount();
|
||||
for (int i = 0; i < numCodecs; i++) {
|
||||
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
|
||||
|
||||
if (!codecInfo.isEncoder()) { // skipp decoder
|
||||
continue;
|
||||
}
|
||||
// select first codec that match a specific MIME type and color format
|
||||
final String[] types = codecInfo.getSupportedTypes();
|
||||
for (int j = 0; j < types.length; j++) {
|
||||
if (types[j].equalsIgnoreCase(mimeType)) {
|
||||
final int format = selectColorFormat(codecInfo, mimeType);
|
||||
if (format > 0) {
|
||||
mColorFormat = format;
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* select color format available on specific codec and we can use.
|
||||
*
|
||||
* @return 0 if no colorFormat is matched
|
||||
*/
|
||||
protected static final int selectColorFormat(final MediaCodecInfo codecInfo, final String mimeType) {
|
||||
int result = 0;
|
||||
final MediaCodecInfo.CodecCapabilities caps;
|
||||
try {
|
||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
|
||||
caps = codecInfo.getCapabilitiesForType(mimeType);
|
||||
} finally {
|
||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
int colorFormat;
|
||||
for (int i = 0; i < caps.colorFormats.length; i++) {
|
||||
colorFormat = caps.colorFormats[i];
|
||||
if (isRecognizedViewoFormat(colorFormat)) {
|
||||
if (result == 0)
|
||||
result = colorFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result == 0)
|
||||
Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* color formats that we can use in this class
|
||||
*/
|
||||
protected static int[] recognizedFormats;
|
||||
|
||||
static {
|
||||
recognizedFormats = new int[]{
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
|
||||
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar,
|
||||
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
|
||||
MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
|
||||
};
|
||||
}
|
||||
|
||||
private static final boolean isRecognizedViewoFormat(final int colorFormat) {
|
||||
final int n = recognizedFormats != null ? recognizedFormats.length : 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (recognizedFormats[i] == colorFormat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private byte[] nv21ToI420(byte[] data, int width, int height) {
|
||||
byte[] ret = new byte[width * height * 3 / 2];
|
||||
int total = width * height;
|
||||
|
||||
ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total); // I420的Y分量
|
||||
ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4); // I420的U分量
|
||||
ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4); // I420的V分量
|
||||
// NV21 YYYYYYYY VUVU
|
||||
bufferY.put(data, 0, total);
|
||||
for (int i = total; i < data.length; i += 2) {
|
||||
bufferV.put(data[i]);
|
||||
bufferU.put(data[i + 1]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private byte[] nv12ToI420(byte[] data, int width, int height) {
|
||||
byte[] ret = new byte[width * height * 3 / 2];
|
||||
int total = width * height;
|
||||
|
||||
ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total); // I420的Y分量
|
||||
ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4); // I420的U分量
|
||||
ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4); // I420的V分量
|
||||
|
||||
// NV12 YYYYYYYY UVUV
|
||||
bufferY.put(data, 0, total);
|
||||
for (int i = total; i < data.length; i += 2) {
|
||||
bufferU.put(data[i]);
|
||||
bufferV.put(data[i + 1]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private byte[] nv12ToNv21(byte[] data, int width, int height) {
|
||||
byte[] ret = new byte[width * height * 3 / 2];
|
||||
int total = width * height;
|
||||
|
||||
ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total); // I420的Y分量
|
||||
ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4); // I420的U分量
|
||||
ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4); // I420的V分量
|
||||
|
||||
// NV12 YYYYYYYY UVUV
|
||||
bufferY.put(data, 0, total);
|
||||
for (int i = total; i < data.length; i += 2) {
|
||||
bufferU.put(data[i]);
|
||||
bufferV.put(data[i + 1]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// YYYYYYYY UVUV(nv21)--> YYYYYYYY VUVU(nv12)
|
||||
private byte[] nv21ToNV12(byte[] nv21, int width, int height) {
|
||||
byte[] ret = new byte[width * height * 3 /2];
|
||||
int framesize = width * height;
|
||||
int i = 0, j = 0;
|
||||
// 拷贝Y分量
|
||||
System.arraycopy(nv21, 0,ret , 0, framesize);
|
||||
// 拷贝UV分量
|
||||
for (j = framesize; j < nv21.length; j += 2) {
|
||||
ret[j+1] = nv21[j+1];
|
||||
ret[j] = nv21[j];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// YYYYYYYY UVUV(nv12)--> YYYYYYYY VUVU(nv21)
|
||||
private byte[] nv12ToNV21(byte[] nv12, int width, int height) {
|
||||
byte[] ret = new byte[width * height * 3 /2];
|
||||
int framesize = width * height;
|
||||
int i = 0, j = 0;
|
||||
// 拷贝Y分量
|
||||
System.arraycopy(nv12, 0,ret , 0, framesize);
|
||||
// 拷贝UV分量
|
||||
for (j = framesize; j < nv12.length; j += 2) {
|
||||
ret[j] = nv12[j+1];
|
||||
ret[j+1] = nv12[j];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package com.serenegiant.usb.encoder.biz;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaMuxer;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**Mp4封装混合器
|
||||
*
|
||||
* Created by jianddongguo on 2017/7/28.
|
||||
*/
|
||||
|
||||
public class Mp4MediaMuxer {
|
||||
private static final boolean VERBOSE = false;
|
||||
private static final String TAG = Mp4MediaMuxer.class.getSimpleName();
|
||||
private String mFilePath;
|
||||
private MediaMuxer mMuxer;
|
||||
private long durationMillis;
|
||||
private int index = 0;
|
||||
private int mVideoTrackIndex = -1;
|
||||
private int mAudioTrackIndex = -1;
|
||||
private long mBeginMillis;
|
||||
private MediaFormat mVideoFormat;
|
||||
private MediaFormat mAudioFormat;
|
||||
private boolean isVoiceClose;
|
||||
|
||||
// 文件路径;文件时长
|
||||
public Mp4MediaMuxer(String path, long durationMillis,boolean isVoiceClose) {
|
||||
String mFilePath;
|
||||
this.isVoiceClose = isVoiceClose;
|
||||
this.durationMillis = durationMillis;
|
||||
if(durationMillis != 0) {
|
||||
mFilePath = path + "-" + index++ + ".mp4";
|
||||
}else{
|
||||
mFilePath = path+".mp4";
|
||||
}
|
||||
Object mux = null;
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
mux = new MediaMuxer(mFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
mMuxer = (MediaMuxer) mux;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void addTrack(MediaFormat format, boolean isVideo) {
|
||||
// now that we have the Magic Goodies, start the muxer
|
||||
if ((!isVoiceClose && mAudioTrackIndex != -1) && mVideoTrackIndex != -1)
|
||||
throw new RuntimeException("already add all tracks");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
int track = mMuxer.addTrack(format);
|
||||
if (VERBOSE)
|
||||
Log.i(TAG, String.format("addTrack %s result %d", isVideo ? "video" : "audio", track));
|
||||
if (isVideo) {
|
||||
mVideoFormat = format;
|
||||
mVideoTrackIndex = track;
|
||||
// 当音频轨道添加
|
||||
// 或者开启静音就start
|
||||
if (isVoiceClose || mAudioTrackIndex != -1) {
|
||||
if (VERBOSE)
|
||||
Log.i(TAG, "both audio and video added,and muxer is started");
|
||||
mMuxer.start();
|
||||
mBeginMillis = System.currentTimeMillis();
|
||||
}
|
||||
} else {
|
||||
mAudioFormat = format;
|
||||
mAudioTrackIndex = track;
|
||||
if (mVideoTrackIndex != -1) {
|
||||
mMuxer.start();
|
||||
mBeginMillis = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void pumpStream(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo, boolean isVideo) {
|
||||
if ((!isVoiceClose && mAudioTrackIndex == -1) || mVideoTrackIndex == -1) {
|
||||
// Log.i(TAG, String.format("pumpStream [%s] but muxer is not start.ignore..", isVideo ? "video" : "audio"));
|
||||
return;
|
||||
}
|
||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
||||
// The codec config data was pulled out and fed to the muxer when we got
|
||||
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
|
||||
} else if (bufferInfo.size != 0) {
|
||||
if (isVideo && mVideoTrackIndex == -1) {
|
||||
throw new RuntimeException("muxer hasn't started");
|
||||
}
|
||||
|
||||
// adjust the ByteBuffer values to match BufferInfo (not needed?)
|
||||
outputBuffer.position(bufferInfo.offset);
|
||||
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
mMuxer.writeSampleData(isVideo ? mVideoTrackIndex : mAudioTrackIndex, outputBuffer, bufferInfo);
|
||||
}
|
||||
// if (VERBOSE)
|
||||
// Log.d(TAG, String.format("sent %s [" + bufferInfo.size + "] with timestamp:[%d] to muxer", isVideo ? "video" : "audio", bufferInfo.presentationTimeUs / 1000));
|
||||
}
|
||||
|
||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||
// if (VERBOSE)
|
||||
// Log.i(TAG, "BUFFER_FLAG_END_OF_STREAM received");
|
||||
}
|
||||
|
||||
if (durationMillis!=0 && System.currentTimeMillis() - mBeginMillis >= durationMillis) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
// if (VERBOSE)
|
||||
// Log.i(TAG, String.format("record file reach expiration.create new file:" + index));
|
||||
mMuxer.stop();
|
||||
mMuxer.release();
|
||||
mMuxer = null;
|
||||
mVideoTrackIndex = mAudioTrackIndex = -1;
|
||||
try {
|
||||
mMuxer = new MediaMuxer(mFilePath + "-" + ++index + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
||||
addTrack(mVideoFormat, true);
|
||||
addTrack(mAudioFormat, false);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void release() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
if (mMuxer != null) {
|
||||
//(!isVoiceClose&&mAudioTrackIndex != -1)
|
||||
if (mVideoTrackIndex != -1) {
|
||||
if (VERBOSE)
|
||||
Log.i(TAG, String.format("muxer is started. now it will be stoped."));
|
||||
try {
|
||||
mMuxer.stop();
|
||||
mMuxer.release();
|
||||
} catch (IllegalStateException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - mBeginMillis <= 1500){
|
||||
new File(mFilePath + "-" + index + ".mp4").delete();
|
||||
}
|
||||
mAudioTrackIndex = mVideoTrackIndex = -1;
|
||||
}else{
|
||||
if (VERBOSE)
|
||||
Log.i(TAG, String.format("muxer is failed to be stoped."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.TextureView;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**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;
|
||||
|
||||
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, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
// 设置屏幕宽高比
|
||||
@Override
|
||||
public void setAspectRatio(final double aspectRatio) {
|
||||
if (aspectRatio < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (mRequestedAspect != aspectRatio) {
|
||||
mRequestedAspect = aspectRatio;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAspectRatio(final int width, final int height) {
|
||||
setAspectRatio(width / (double) height);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.serenegiant.usb.widget;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.serenegiant.usb.encoder.IVideoEncoder;
|
||||
|
||||
public interface CameraViewInterface extends IAspectRatioView {
|
||||
public interface Callback {
|
||||
public void onSurfaceCreated(CameraViewInterface view, Surface surface);
|
||||
public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height);
|
||||
public void onSurfaceDestroy(CameraViewInterface view, Surface surface);
|
||||
}
|
||||
public void onPause();
|
||||
public void onResume();
|
||||
public void setCallback(Callback callback);
|
||||
public SurfaceTexture getSurfaceTexture();
|
||||
public Surface getSurface();
|
||||
public boolean hasSurface();
|
||||
public void setVideoEncoder(final IVideoEncoder encoder);
|
||||
public Bitmap captureStillImage(int width,int height);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.serenegiant.usb.widget;
|
||||
|
||||
public interface IAspectRatioView {
|
||||
void setAspectRatio(double var1);
|
||||
|
||||
void setAspectRatio(int var1, int var2);
|
||||
|
||||
double getAspectRatio();
|
||||
}
|
||||
@@ -1,575 +0,0 @@
|
||||
package com.serenegiant.usb.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import com.serenegiant.glutils.EGLBase;
|
||||
import com.serenegiant.glutils.GLDrawer2D;
|
||||
import com.serenegiant.glutils.es1.GLHelper;
|
||||
import com.serenegiant.usb.encoder.IVideoEncoder;
|
||||
import com.serenegiant.usb.encoder.MediaEncoder;
|
||||
import com.serenegiant.usb.encoder.MediaVideoEncoder;
|
||||
import com.serenegiant.utils.FpsCounter;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public class UVCCameraTextureView extends AspectRatioTextureView // API >= 14
|
||||
implements TextureView.SurfaceTextureListener, CameraViewInterface {
|
||||
|
||||
private static final boolean DEBUG = true; // TODO set false on release
|
||||
private static final String TAG = "UVCCameraTextureView";
|
||||
|
||||
private boolean mHasSurface;
|
||||
private RenderHandler mRenderHandler;
|
||||
private final Object mCaptureSync = new Object();
|
||||
private Bitmap mTempBitmap;
|
||||
private boolean mReqesutCaptureStillImage;
|
||||
private Callback mCallback;
|
||||
// Camera分辨率宽度
|
||||
|
||||
|
||||
/** for calculation of frame rate */
|
||||
private final FpsCounter mFpsCounter = new FpsCounter();
|
||||
|
||||
public UVCCameraTextureView(final Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public UVCCameraTextureView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public UVCCameraTextureView(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setSurfaceTextureListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
if (DEBUG) Log.v(TAG, "onResume:");
|
||||
if (mHasSurface) {
|
||||
mRenderHandler = RenderHandler.createHandler(mFpsCounter, super.getSurfaceTexture(), getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (DEBUG) Log.v(TAG, "onPause:");
|
||||
if (mRenderHandler != null) {
|
||||
mRenderHandler.release();
|
||||
mRenderHandler = null;
|
||||
}
|
||||
if (mTempBitmap != null) {
|
||||
mTempBitmap.recycle();
|
||||
mTempBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
|
||||
if (DEBUG) Log.i(TAG, "onSurfaceTextureAvailable:" + surface);
|
||||
if (mRenderHandler == null) {
|
||||
mRenderHandler = RenderHandler.createHandler(mFpsCounter, surface, width, height);
|
||||
} else {
|
||||
mRenderHandler.resize(width, height);
|
||||
}
|
||||
mHasSurface = true;
|
||||
if (mCallback != null) {
|
||||
mCallback.onSurfaceCreated(this, getSurface());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(final SurfaceTexture surface, final int width, final int height) {
|
||||
if (DEBUG) Log.i(TAG, "onSurfaceTextureSizeChanged:" + surface);
|
||||
if (mRenderHandler != null) {
|
||||
mRenderHandler.resize(width, height);
|
||||
}
|
||||
if (mCallback != null) {
|
||||
mCallback.onSurfaceChanged(this, getSurface(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) {
|
||||
if (DEBUG) Log.i(TAG, "onSurfaceTextureDestroyed:" + surface);
|
||||
if (mRenderHandler != null) {
|
||||
mRenderHandler.release();
|
||||
mRenderHandler = null;
|
||||
}
|
||||
mHasSurface = false;
|
||||
if (mCallback != null) {
|
||||
mCallback.onSurfaceDestroy(this, getSurface());
|
||||
}
|
||||
if (mPreviewSurface != null) {
|
||||
mPreviewSurface.release();
|
||||
mPreviewSurface = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(final SurfaceTexture surface) {
|
||||
synchronized (mCaptureSync) {
|
||||
if (mReqesutCaptureStillImage) {
|
||||
mReqesutCaptureStillImage = false;
|
||||
if (mTempBitmap == null)
|
||||
mTempBitmap = getBitmap();
|
||||
else
|
||||
getBitmap(mTempBitmap);
|
||||
mCaptureSync.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSurface() {
|
||||
return mHasSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* capture preview image as a bitmap
|
||||
* this method blocks current thread until bitmap is ready
|
||||
* if you call this method at almost same time from different thread,
|
||||
* the returned bitmap will be changed while you are processing the bitmap
|
||||
* (because we return same instance of bitmap on each call for memory saving)
|
||||
* if you need to call this method from multiple thread,
|
||||
* you should change this method(copy and return)
|
||||
*/
|
||||
@Override
|
||||
public Bitmap captureStillImage(int width,int height) {
|
||||
synchronized (mCaptureSync) {
|
||||
mReqesutCaptureStillImage = true;
|
||||
try {
|
||||
mCaptureSync.wait();
|
||||
} catch (final InterruptedException e) {
|
||||
}
|
||||
return mTempBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceTexture getSurfaceTexture() {
|
||||
return mRenderHandler != null ? mRenderHandler.getPreviewTexture() : null;
|
||||
}
|
||||
|
||||
private Surface mPreviewSurface;
|
||||
@Override
|
||||
public Surface getSurface() {
|
||||
if (DEBUG) Log.v(TAG, "getSurface:hasSurface=" + mHasSurface);
|
||||
if (mPreviewSurface == null) {
|
||||
final SurfaceTexture st = getSurfaceTexture();
|
||||
if (st != null) {
|
||||
mPreviewSurface = new Surface(st);
|
||||
}
|
||||
}
|
||||
return mPreviewSurface;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoEncoder(final IVideoEncoder encoder) {
|
||||
if (mRenderHandler != null)
|
||||
mRenderHandler.setVideoEncoder(encoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(final Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void resetFps() {
|
||||
mFpsCounter.reset();
|
||||
}
|
||||
|
||||
/** update frame rate of image processing */
|
||||
public void updateFps() {
|
||||
mFpsCounter.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* get current frame rate of image processing
|
||||
* @return
|
||||
*/
|
||||
public float getFps() {
|
||||
return mFpsCounter.getFps();
|
||||
}
|
||||
|
||||
/**
|
||||
* get total frame rate from start
|
||||
* @return
|
||||
*/
|
||||
public float getTotalFps() {
|
||||
return mFpsCounter.getTotalFps();
|
||||
}
|
||||
|
||||
/**
|
||||
* render camera frames on this view on a private thread
|
||||
* @author saki
|
||||
*
|
||||
*/
|
||||
private static final class RenderHandler extends Handler
|
||||
implements SurfaceTexture.OnFrameAvailableListener {
|
||||
|
||||
private static final int MSG_REQUEST_RENDER = 1;
|
||||
private static final int MSG_SET_ENCODER = 2;
|
||||
private static final int MSG_CREATE_SURFACE = 3;
|
||||
private static final int MSG_RESIZE = 4;
|
||||
private static final int MSG_TERMINATE = 9;
|
||||
|
||||
private RenderThread mThread;
|
||||
private boolean mIsActive = true;
|
||||
private final FpsCounter mFpsCounter;
|
||||
|
||||
public static final RenderHandler createHandler(final FpsCounter counter,
|
||||
final SurfaceTexture surface, final int width, final int height) {
|
||||
|
||||
final RenderThread thread = new RenderThread(counter, surface, width, height);
|
||||
thread.start();
|
||||
return thread.getHandler();
|
||||
}
|
||||
|
||||
private RenderHandler(final FpsCounter counter, final RenderThread thread) {
|
||||
mThread = thread;
|
||||
mFpsCounter = counter;
|
||||
}
|
||||
|
||||
public final void setVideoEncoder(final IVideoEncoder encoder) {
|
||||
if (DEBUG) Log.v(TAG, "setVideoEncoder:");
|
||||
if (mIsActive)
|
||||
sendMessage(obtainMessage(MSG_SET_ENCODER, encoder));
|
||||
}
|
||||
|
||||
public final SurfaceTexture getPreviewTexture() {
|
||||
if (DEBUG) Log.v(TAG, "getPreviewTexture:");
|
||||
if (mIsActive) {
|
||||
synchronized (mThread.mSync) {
|
||||
sendEmptyMessage(MSG_CREATE_SURFACE);
|
||||
try {
|
||||
mThread.mSync.wait();
|
||||
} catch (final InterruptedException e) {
|
||||
}
|
||||
return mThread.mPreviewSurface;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void resize(final int width, final int height) {
|
||||
if (DEBUG) Log.v(TAG, "resize:");
|
||||
if (mIsActive) {
|
||||
synchronized (mThread.mSync) {
|
||||
sendMessage(obtainMessage(MSG_RESIZE, width, height));
|
||||
try {
|
||||
mThread.mSync.wait();
|
||||
} catch (final InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void release() {
|
||||
if (DEBUG) Log.v(TAG, "release:");
|
||||
if (mIsActive) {
|
||||
mIsActive = false;
|
||||
removeMessages(MSG_REQUEST_RENDER);
|
||||
removeMessages(MSG_SET_ENCODER);
|
||||
sendEmptyMessage(MSG_TERMINATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onFrameAvailable(final SurfaceTexture surfaceTexture) {
|
||||
if (mIsActive) {
|
||||
mFpsCounter.count();
|
||||
sendEmptyMessage(MSG_REQUEST_RENDER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void handleMessage(final Message msg) {
|
||||
if (mThread == null) return;
|
||||
switch (msg.what) {
|
||||
case MSG_REQUEST_RENDER:
|
||||
mThread.onDrawFrame();
|
||||
break;
|
||||
case MSG_SET_ENCODER:
|
||||
mThread.setEncoder((MediaEncoder)msg.obj);
|
||||
break;
|
||||
case MSG_CREATE_SURFACE:
|
||||
mThread.updatePreviewSurface();
|
||||
break;
|
||||
case MSG_RESIZE:
|
||||
mThread.resize(msg.arg1, msg.arg2);
|
||||
break;
|
||||
case MSG_TERMINATE:
|
||||
Looper.myLooper().quit();
|
||||
mThread = null;
|
||||
break;
|
||||
default:
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RenderThread extends Thread {
|
||||
private final Object mSync = new Object();
|
||||
private final SurfaceTexture mSurface;
|
||||
private RenderHandler mHandler;
|
||||
private EGLBase mEgl;
|
||||
/** IEglSurface instance related to this TextureView */
|
||||
private EGLBase.IEglSurface mEglSurface;
|
||||
private GLDrawer2D mDrawer;
|
||||
private int mTexId = -1;
|
||||
/** SurfaceTexture instance to receive video images */
|
||||
private SurfaceTexture mPreviewSurface;
|
||||
private final float[] mStMatrix = new float[16];
|
||||
private MediaEncoder mEncoder;
|
||||
private int mViewWidth, mViewHeight;
|
||||
private final FpsCounter mFpsCounter;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
* @param surface: drawing surface came from TexureView
|
||||
*/
|
||||
public RenderThread(final FpsCounter fpsCounter, final SurfaceTexture surface, final int width, final int height) {
|
||||
mFpsCounter = fpsCounter;
|
||||
mSurface = surface;
|
||||
mViewWidth = width;
|
||||
mViewHeight = height;
|
||||
setName("RenderThread");
|
||||
}
|
||||
|
||||
public final RenderHandler getHandler() {
|
||||
if (DEBUG) Log.v(TAG, "RenderThread#getHandler:");
|
||||
synchronized (mSync) {
|
||||
// create rendering thread
|
||||
if (mHandler == null)
|
||||
try {
|
||||
mSync.wait();
|
||||
} catch (final InterruptedException e) {
|
||||
}
|
||||
}
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
public void resize(final int width, final int height) {
|
||||
if (((width > 0) && (width != mViewWidth)) || ((height > 0) && (height != mViewHeight))) {
|
||||
mViewWidth = width;
|
||||
mViewHeight = height;
|
||||
updatePreviewSurface();
|
||||
} else {
|
||||
synchronized (mSync) {
|
||||
mSync.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void updatePreviewSurface() {
|
||||
if (DEBUG) Log.i(TAG, "RenderThread#updatePreviewSurface:");
|
||||
synchronized (mSync) {
|
||||
if (mPreviewSurface != null) {
|
||||
if (DEBUG) Log.d(TAG, "updatePreviewSurface:release mPreviewSurface");
|
||||
mPreviewSurface.setOnFrameAvailableListener(null);
|
||||
mPreviewSurface.release();
|
||||
mPreviewSurface = null;
|
||||
}
|
||||
mEglSurface.makeCurrent();
|
||||
if (mTexId >= 0) {
|
||||
mDrawer.deleteTex(mTexId);
|
||||
}
|
||||
// create texture and SurfaceTexture for input from camera
|
||||
mTexId = mDrawer.initTex();
|
||||
if (DEBUG) Log.v(TAG, "updatePreviewSurface:tex_id=" + mTexId);
|
||||
mPreviewSurface = new SurfaceTexture(mTexId);
|
||||
mPreviewSurface.setDefaultBufferSize(mViewWidth, mViewHeight);
|
||||
mPreviewSurface.setOnFrameAvailableListener(mHandler);
|
||||
// notify to caller thread that previewSurface is ready
|
||||
mSync.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public final void setEncoder(final MediaEncoder encoder) {
|
||||
if (DEBUG) Log.v(TAG, "RenderThread#setEncoder:encoder=" + encoder);
|
||||
if (encoder != null && (encoder instanceof MediaVideoEncoder)) {
|
||||
((MediaVideoEncoder)encoder).setEglContext(mEglSurface.getContext(), mTexId);
|
||||
}
|
||||
mEncoder = encoder;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now you can get frame data as ByteBuffer(as YUV/RGB565/RGBX/NV21 pixel format) using IFrameCallback interface
|
||||
* with UVCCamera#setFrameCallback instead of using following code samples.
|
||||
*/
|
||||
/* // for part1
|
||||
private static final int BUF_NUM = 1;
|
||||
private static final int BUF_STRIDE = 640 * 480;
|
||||
private static final int BUF_SIZE = BUF_STRIDE * BUF_NUM;
|
||||
int cnt = 0;
|
||||
int offset = 0;
|
||||
final int pixels[] = new int[BUF_SIZE];
|
||||
final IntBuffer buffer = IntBuffer.wrap(pixels); */
|
||||
/* // for part2
|
||||
private ByteBuffer buf = ByteBuffer.allocateDirect(640 * 480 * 4);
|
||||
*/
|
||||
/**
|
||||
* draw a frame (and request to draw for video capturing if it is necessary)
|
||||
*/
|
||||
public final void onDrawFrame() {
|
||||
mEglSurface.makeCurrent();
|
||||
// update texture(came from camera)
|
||||
mPreviewSurface.updateTexImage();
|
||||
// get texture matrix
|
||||
mPreviewSurface.getTransformMatrix(mStMatrix);
|
||||
// notify video encoder if it exist
|
||||
if (mEncoder != null) {
|
||||
// notify to capturing thread that the camera frame is available.
|
||||
if (mEncoder instanceof MediaVideoEncoder)
|
||||
((MediaVideoEncoder)mEncoder).frameAvailableSoon(mStMatrix);
|
||||
else
|
||||
mEncoder.frameAvailableSoon();
|
||||
}
|
||||
// draw to preview screen
|
||||
mDrawer.draw(mTexId, mStMatrix, 0);
|
||||
mEglSurface.swap();
|
||||
/* // sample code to read pixels into Buffer and save as a Bitmap (part1)
|
||||
buffer.position(offset);
|
||||
GLES20.glReadPixels(0, 0, 640, 480, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);
|
||||
if (++cnt == 100) { // save as a Bitmap, only once on this sample code
|
||||
// if you save every frame as a Bitmap, app will crash by Out of Memory exception...
|
||||
Log.i(TAG, "Capture image using glReadPixels:offset=" + offset);
|
||||
final Bitmap bitmap = createBitmap(pixels,offset, 640, 480);
|
||||
final File outputFile = MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".png");
|
||||
try {
|
||||
final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(outputFile));
|
||||
try {
|
||||
try {
|
||||
bitmap.compress(CompressFormat.PNG, 100, os);
|
||||
os.flush();
|
||||
bitmap.recycle();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
} finally {
|
||||
os.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
offset = (offset + BUF_STRIDE) % BUF_SIZE;
|
||||
*/
|
||||
/* // sample code to read pixels into Buffer and save as a Bitmap (part2)
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN); // it is enough to call this only once.
|
||||
GLES20.glReadPixels(0, 0, 640, 480, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
|
||||
buf.rewind();
|
||||
if (++cnt == 100) { // save as a Bitmap, only once on this sample code
|
||||
// if you save every frame as a Bitmap, app will crash by Out of Memory exception...
|
||||
final File outputFile = MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".png");
|
||||
BufferedOutputStream os = null;
|
||||
try {
|
||||
try {
|
||||
os = new BufferedOutputStream(new FileOutputStream(outputFile));
|
||||
Bitmap bmp = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888);
|
||||
bmp.copyPixelsFromBuffer(buf);
|
||||
bmp.compress(Bitmap.CompressFormat.PNG, 90, os);
|
||||
bmp.recycle();
|
||||
} finally {
|
||||
if (os != null) os.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/* // sample code to read pixels into IntBuffer and save as a Bitmap (part1)
|
||||
private static Bitmap createBitmap(final int[] pixels, final int offset, final int width, final int height) {
|
||||
final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
paint.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(new float[] {
|
||||
0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 0,
|
||||
1, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 0
|
||||
})));
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
matrix.postScale(1.0f, -1.0f);
|
||||
matrix.postTranslate(0, height);
|
||||
canvas.concat(matrix);
|
||||
|
||||
canvas.drawBitmap(pixels, offset, width, 0, 0, width, height, false, paint);
|
||||
|
||||
return bitmap;
|
||||
} */
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
Log.d(TAG, getName() + " started");
|
||||
init();
|
||||
Looper.prepare();
|
||||
synchronized (mSync) {
|
||||
mHandler = new RenderHandler(mFpsCounter, this);
|
||||
mSync.notify();
|
||||
}
|
||||
|
||||
Looper.loop();
|
||||
|
||||
Log.d(TAG, getName() + " finishing");
|
||||
release();
|
||||
synchronized (mSync) {
|
||||
mHandler = null;
|
||||
mSync.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private final void init() {
|
||||
if (DEBUG) Log.v(TAG, "RenderThread#init:");
|
||||
// create EGLContext for this thread
|
||||
mEgl = EGLBase.createFrom(null, false, false);
|
||||
mEglSurface = mEgl.createFromSurface(mSurface);
|
||||
mEglSurface.makeCurrent();
|
||||
// create drawing object
|
||||
mDrawer = new GLDrawer2D(true);
|
||||
}
|
||||
|
||||
private final void release() {
|
||||
if (DEBUG) Log.v(TAG, "RenderThread#release:");
|
||||
if (mDrawer != null) {
|
||||
mDrawer.release();
|
||||
mDrawer = null;
|
||||
}
|
||||
if (mPreviewSurface != null) {
|
||||
mPreviewSurface.release();
|
||||
mPreviewSurface = null;
|
||||
}
|
||||
if (mTexId >= 0) {
|
||||
GLHelper.deleteTex(mTexId);
|
||||
mTexId = -1;
|
||||
}
|
||||
if (mEglSurface != null) {
|
||||
mEglSurface.release();
|
||||
mEglSurface = null;
|
||||
}
|
||||
if (mEgl != null) {
|
||||
mEgl.release();
|
||||
mEgl = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,458 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,515 +0,0 @@
|
||||
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<Request> mRequestPool; // FIXME これはArrayListにした方が速いかも
|
||||
private final LinkedBlockingDeque<Request> mRequestQueue;
|
||||
private volatile boolean mIsRunning, mFinished;
|
||||
private Thread mWorkerThread;
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
* プール&キューのサイズは無制限
|
||||
* プールは空で生成
|
||||
*/
|
||||
public MessageTask() {
|
||||
mMaxRequest = -1;
|
||||
mRequestPool = new LinkedBlockingQueue<Request>();
|
||||
mRequestQueue = new LinkedBlockingDeque<Request>();
|
||||
}
|
||||
|
||||
/**
|
||||
* コンストラクタ
|
||||
* プール&キューのサイズは無制限
|
||||
* @param init_num プールするRequestの初期数を指定
|
||||
*/
|
||||
public MessageTask(final int init_num) {
|
||||
mMaxRequest = -1;
|
||||
mRequestPool = new LinkedBlockingQueue<Request>();
|
||||
mRequestQueue = new LinkedBlockingDeque<Request>();
|
||||
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<Request>(max_request);
|
||||
mRequestQueue = new LinkedBlockingDeque<Request>(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();
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package com.serenegiant.utils;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionGroupInfo;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class PermissionCheck {
|
||||
|
||||
public static final void dumpPermissions(@Nullable final Context context) {
|
||||
if (context == null) return;
|
||||
try {
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
final List<PermissionGroupInfo> list = pm.getAllPermissionGroups(PackageManager.GET_META_DATA);
|
||||
for (final PermissionGroupInfo info : list) {
|
||||
Log.d("PermissionCheck", info.name);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.w("", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* パーミッションを確認
|
||||
* @param context
|
||||
* @param permissionName
|
||||
* @return 指定したパーミッションがあればtrue
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public static boolean hasPermission(@Nullable final Context context, final String permissionName) {
|
||||
if (context == null) return false;
|
||||
boolean result = false;
|
||||
try {
|
||||
final int check;
|
||||
if (BuildCheck.isMarshmallow()) {
|
||||
check = context.checkSelfPermission(permissionName);
|
||||
} else {
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
check = pm.checkPermission(permissionName, context.getPackageName());
|
||||
}
|
||||
switch (check) {
|
||||
case PackageManager.PERMISSION_DENIED:
|
||||
break;
|
||||
case PackageManager.PERMISSION_GRANTED:
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.w("", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 録音のミッションがあるかどうかを確認
|
||||
* @param context
|
||||
* @return 録音のパーミッションがあればtrue
|
||||
*/
|
||||
public static boolean hasAudio(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.RECORD_AUDIO);
|
||||
}
|
||||
|
||||
/**
|
||||
* ネットワークへのアクセスパーミッションがあるかどうかを確認
|
||||
* @param context
|
||||
* @return ネットワークへのアクセスパーミッションがあればtrue
|
||||
*/
|
||||
public static boolean hasNetwork(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.INTERNET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部ストレージへの書き込みパーミッションがあるかどうかを確認
|
||||
* @param context
|
||||
* @return 外部ストレージへの書き込みパーミッションがあればtrue
|
||||
*/
|
||||
public static boolean hasWriteExternalStorage(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部ストレージからの読み込みパーミッションがあるかどうかを確認
|
||||
* @param context
|
||||
* @return 外部ストレージへの読み込みパーミッションがあればtrue
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
public static boolean hasReadExternalStorage(@Nullable final Context context) {
|
||||
if (BuildCheck.isAndroid4())
|
||||
return hasPermission(context, permission.READ_EXTERNAL_STORAGE);
|
||||
else
|
||||
return hasPermission(context, permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 位置情報アクセスのパーミッションが有るかどうかを確認
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasAccessLocation(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.ACCESS_COARSE_LOCATION)
|
||||
&& hasPermission(context, permission.ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 低精度位置情報アクセスのパーミッションが有るかどうかを確認
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasAccessCoarseLocation(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.ACCESS_COARSE_LOCATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 高精度位置情報アクセスのパーミッションが有るかどうかを確認
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasAccessFineLocation(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* カメラへアクセス可能かどうか
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasCamera(@Nullable final Context context) {
|
||||
return hasPermission(context, permission.CAMERA);
|
||||
}
|
||||
|
||||
/**
|
||||
* アプリの詳細設定へ遷移させる(パーミッションを取得できなかった時など)
|
||||
* @param context
|
||||
*/
|
||||
public static void openSettings(@NonNull final Context context) {
|
||||
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
final Uri uri = Uri.fromParts("package", context.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* AndroidManifest.xmlに設定されているはずのパーミッションをチェックする
|
||||
* @param context
|
||||
* @param expectations
|
||||
* @return 空リストなら全てのパーミッションが入っていた,
|
||||
* @throws IllegalArgumentException
|
||||
* @throws PackageManager.NameNotFoundException
|
||||
*/
|
||||
public static List<String> missingPermissions(@NonNull final Context context, @NonNull final String[] expectations) throws IllegalArgumentException, PackageManager.NameNotFoundException {
|
||||
return missingPermissions(context, new ArrayList<String>(Arrays.asList(expectations)));
|
||||
}
|
||||
|
||||
/**
|
||||
* AndroidManifest.xmlに設定されているはずのパーミッションをチェックする
|
||||
* @param context
|
||||
* @param expectations
|
||||
* @return 空リストなら全てのパーミッションが入っていた,
|
||||
* @throws IllegalArgumentException
|
||||
* @throws PackageManager.NameNotFoundException
|
||||
*/
|
||||
public static List<String> missingPermissions(@NonNull final Context context, @NonNull final List<String> expectations) throws IllegalArgumentException, PackageManager.NameNotFoundException {
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
final PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
final String[] info = pi.requestedPermissions;
|
||||
if (info != null) {
|
||||
for (String i : info) {
|
||||
expectations.remove(i);
|
||||
}
|
||||
}
|
||||
return expectations;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.easydarwin.sw;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class JNIUtil {
|
||||
|
||||
static {
|
||||
System.loadLibrary("Utils");
|
||||
}
|
||||
|
||||
/**
|
||||
* 都是Y:U:V = 4:1:1但 U与 V顺序相反。变换可逆
|
||||
*
|
||||
* @param buffer
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public static void yV12ToYUV420P(byte[] buffer, int width, int height) {
|
||||
callMethod("YV12ToYUV420P", null, buffer, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 都是Y:U+V = 4:2,但是这两者U、V方向相反。变换可逆
|
||||
*
|
||||
* @param buffer
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public static void nV21To420SP(byte[] buffer, int width, int height) {
|
||||
callMethod("NV21To420SP", null, buffer, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转1个字节为单位的矩阵
|
||||
*
|
||||
* @param data 要旋转的矩阵
|
||||
* @param offset 偏移量
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param degree 旋转度数
|
||||
*/
|
||||
public static void rotateMatrix(byte[] data, int offset, int width, int height, int degree) {
|
||||
callMethod("RotateByteMatrix", null, data, offset, width, height, degree);
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转2个字节为单位的矩阵
|
||||
*
|
||||
* @param data 要旋转的矩阵
|
||||
* @param offset 偏移量
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param degree 旋转度数
|
||||
*/
|
||||
public static void rotateShortMatrix(byte[] data, int offset, int width, int height, int degree) {
|
||||
callMethod("RotateShortMatrix", null, data, offset, width, height, degree);
|
||||
}
|
||||
|
||||
private static native void callMethod(String methodName, Object[] returnValue, Object... params);
|
||||
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package org.easydarwin.sw;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Created by jiangdg on 2020/1/11.
|
||||
*/
|
||||
|
||||
public class TxtOverlay {
|
||||
|
||||
static {
|
||||
System.loadLibrary("TxtOverlay");
|
||||
}
|
||||
|
||||
private static TxtOverlay instance;
|
||||
private final Context context;
|
||||
private long ctx;
|
||||
|
||||
private TxtOverlay(Context context){
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public static TxtOverlay getInstance() {
|
||||
if(instance == null) {
|
||||
throw new IllegalArgumentException("please call install in your application!");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void install(Context context) {
|
||||
if(instance == null) {
|
||||
instance = new TxtOverlay(context.getApplicationContext());
|
||||
|
||||
File youyuan = context.getFileStreamPath("SIMYOU.ttf");
|
||||
if (!youyuan.exists()){
|
||||
AssetManager am = context.getAssets();
|
||||
try {
|
||||
InputStream is = am.open("zk/SIMYOU.ttf");
|
||||
FileOutputStream os = context.openFileOutput("SIMYOU.ttf", Context.MODE_PRIVATE);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = 0;
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
os.close();
|
||||
is.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void init(int width, int height) {
|
||||
File youyuan = context.getFileStreamPath("SIMYOU.ttf");
|
||||
if (!youyuan.exists()){
|
||||
throw new IllegalArgumentException("the font file must be exists,please call install before!");
|
||||
}
|
||||
ctx = txtOverlayInit(width, height,youyuan.getAbsolutePath());
|
||||
}
|
||||
|
||||
public void overlay(byte[] data,
|
||||
String txt) {
|
||||
// txt = "drawtext=fontfile="+context.getFileStreamPath("SIMYOU.ttf")+": text='EasyPusher 2017':x=(w-text_w)/2:y=H-60 :fontcolor=white :box=1:boxcolor=0x00000000@0.3";
|
||||
// txt = "movie=/sdcard/qrcode.png [logo];[in][logo] "
|
||||
// + "overlay=" + 0 + ":" + 0
|
||||
// + " [out]";
|
||||
// if (ctx == 0) throw new RuntimeException("init should be called at first!");
|
||||
if (ctx == 0) return;
|
||||
txtOverlay(ctx, data, txt);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (ctx == 0) return;
|
||||
txtOverlayRelease(ctx);
|
||||
ctx = 0;
|
||||
}
|
||||
|
||||
|
||||
private static native long txtOverlayInit(int width, int height, String fonts);
|
||||
|
||||
private static native void txtOverlay(long ctx, byte[] data, String txt);
|
||||
|
||||
private static native void txtOverlayRelease(long ctx);
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user