Merge pull request 'rtmp_update' (#40) from rtmp_update into master

Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/40
This commit is contained in:
Schoumi 2022-10-04 17:52:05 +02:00
commit 06ab71610f
361 changed files with 20640 additions and 9418 deletions

View File

@ -1,9 +0,0 @@
---
kind: pipeline
type: exec
name: default
steps:
- name: build
commands:
- bash ./gradlew assembleDebug

View File

@ -19,7 +19,6 @@
<option value="$PROJECT_DIR$/rtsp" /> <option value="$PROJECT_DIR$/rtsp" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@ -26,5 +26,10 @@
<option name="name" value="maven" /> <option name="name" value="maven" />
<option name="url" value="https://jitpack.io" /> <option name="url" value="https://jitpack.io" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component> </component>
</project> </project>

View File

@ -5,6 +5,13 @@
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" /> <configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations> </configurations>
</component> </component>
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../../layout/custom_preview.xml" value="0.14448441247002397" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>

9
.woodpecker.yml Normal file
View File

@ -0,0 +1,9 @@
pipeline:
build:
image: eclipse-temurin:11
commands:
- bash ./gradlew assembleDebug
environment:
- ANDROID_HOME=/mnt/sdk
volumes:
- /home/woodpecker/sdk:/mnt/sdk

View File

@ -5,13 +5,13 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs.kotlin' apply plugin: 'androidx.navigation.safeargs.kotlin'
android { android {
compileSdkVersion 31 compileSdkVersion 32
buildToolsVersion "31.0.0" buildToolsVersion "32.0.0"
defaultConfig { defaultConfig {
applicationId "fr.mobdev.peertubelive" applicationId "fr.mobdev.peertubelive"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 32
versionCode 2 versionCode 2
versionName "1.1" versionName "1.1"
@ -39,6 +39,7 @@ android {
buildFeatures { buildFeatures {
dataBinding true dataBinding true
} }
namespace 'fr.mobdev.peertubelive'
} }

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="fr.mobdev.peertubelive">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@ -32,12 +32,14 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import com.pedro.encoder.input.video.CameraHelper import com.pedro.encoder.input.video.CameraHelper
import com.pedro.rtmp.utils.ConnectCheckerRtmp
import com.pedro.rtplibrary.rtmp.RtmpCamera2 import com.pedro.rtplibrary.rtmp.RtmpCamera2
import net.ossrs.rtmp.ConnectCheckerRtmp
import fr.mobdev.peertubelive.R import fr.mobdev.peertubelive.R
import fr.mobdev.peertubelive.databinding.StreamBinding import fr.mobdev.peertubelive.databinding.StreamBinding
import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA
import fr.mobdev.peertubelive.objects.StreamData import fr.mobdev.peertubelive.objects.StreamData
import java.util.*
import kotlin.collections.ArrayList
class StreamActivity : AppCompatActivity() { class StreamActivity : AppCompatActivity() {
@ -50,10 +52,11 @@ class StreamActivity : AppCompatActivity() {
private var surfaceInit: Boolean = false private var surfaceInit: Boolean = false
private var permissionGiven: Boolean = false private var permissionGiven: Boolean = false
private var streamIsActive: Boolean = false private var streamIsActive: Boolean = false
private var screenOrientation: Int = 0 private var screenOrientation: Int = -1
private var lastScreenOrientation: Int = 0 private var lastScreenOrientation: Int = 0
private var orientationCounter: Int = 0 private var rotationIsEnabled: Boolean = true
private var rotationIsLanternEnabled: Boolean = true
private var orientationTimer: Timer = Timer()
companion object { companion object {
const val BACKGROUND :Int = 1 const val BACKGROUND :Int = 1
@ -69,46 +72,11 @@ class StreamActivity : AppCompatActivity() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding = DataBindingUtil.setContentView(this, R.layout.stream) binding = DataBindingUtil.setContentView(this, R.layout.stream)
orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){ orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){
override fun onOrientationChanged(orientation: Int) { override fun onOrientationChanged(orientation: Int) {
if(orientation < 0 || !rotationIsLanternEnabled) handlerOrientation(orientation)
return
var localOrientation: Int
localOrientation = when (orientation) {
in 45..135 -> {
90
}
in 135..225 -> {
180
}
in 225..315 -> {
270
}
else -> {
0
}
}
if(localOrientation != lastScreenOrientation) {
lastScreenOrientation = localOrientation
orientationCounter = 0
} else {
orientationCounter++
}
if (lastScreenOrientation != screenOrientation && orientationCounter > 30) {
screenOrientation = lastScreenOrientation
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
if (screenOrientation == 90) {
localOrientation = 270
} else if(screenOrientation == 270) {
localOrientation = 90
}
binding.flash.rotation = localOrientation.toFloat()
binding.muteMicro.rotation = localOrientation.toFloat()
binding.switchCamera.rotation = localOrientation.toFloat()
}
} }
} }
orientationEventListener.enable() orientationEventListener.enable()
@ -144,14 +112,11 @@ class StreamActivity : AppCompatActivity() {
binding.flash.visibility = View.GONE binding.flash.visibility = View.GONE
binding.rotation.setOnClickListener { binding.rotation.setOnClickListener {
if (rotationIsLanternEnabled) { if (rotationIsEnabled)
rotationIsLanternEnabled = !rotationIsLanternEnabled
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24) binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
} else
else {
rotationIsLanternEnabled = !rotationIsLanternEnabled
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24) binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
} rotationIsEnabled = !rotationIsEnabled
} }
binding.rotation.visibility = View.GONE binding.rotation.visibility = View.GONE
binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback { binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
@ -260,6 +225,52 @@ class StreamActivity : AppCompatActivity() {
} }
} }
private fun handlerOrientation(orientation: Int) {
if(orientation < 0 || !rotationIsEnabled) {
return
}
var localOrientation: Int
localOrientation = when (orientation) {
in 45..135 -> {
90
}
in 135..225 -> {
180
}
in 225..315 -> {
270
}
else -> {
0
}
}
if(localOrientation != lastScreenOrientation) {
lastScreenOrientation = localOrientation
orientationTimer.cancel()
orientationTimer.purge()
orientationTimer = Timer()
orientationTimer.schedule(object : TimerTask() {
override fun run() {
if (lastScreenOrientation != screenOrientation) {
screenOrientation = lastScreenOrientation
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
if (screenOrientation == 90) {
localOrientation = 270
} else if(screenOrientation == 270) {
localOrientation = 90
}
binding.flash.rotation = localOrientation.toFloat()
binding.muteMicro.rotation = localOrientation.toFloat()
binding.switchCamera.rotation = localOrientation.toFloat()
binding.rotation.rotation = localOrientation.toFloat()
}
}
},3000)
}
}
private fun startStream() { private fun startStream() {
streamIsActive = true streamIsActive = true
binding.permissionInfo.visibility = View.GONE binding.permissionInfo.visibility = View.GONE
@ -271,6 +282,10 @@ class StreamActivity : AppCompatActivity() {
binding.flash.visibility = View.VISIBLE binding.flash.visibility = View.VISIBLE
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp { val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
override fun onConnectionStartedRtmp(rtmpUrl: String) {
}
override fun onConnectionSuccessRtmp() { override fun onConnectionSuccessRtmp() {
runOnUiThread { runOnUiThread {
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show(); Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
@ -347,7 +362,8 @@ class StreamActivity : AppCompatActivity() {
//start stream //start stream
if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),false,CameraHelper.getCameraOrientation(this))) { if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),CameraHelper.getCameraOrientation(this),true)) {
println("peertubeurl "+streamData.url+"/"+streamData.key)
rtmpCamera2.startStream(streamData.url+"/"+streamData.key) rtmpCamera2.startStream(streamData.url+"/"+streamData.key)
} else { } else {
/**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */ /**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */

View File

@ -41,7 +41,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:id="@+id/surfaceView" android:id="@+id/surfaceView"
app:keepAspectRatio="true" app:keepAspectRatio="true"
app:aspectRatioMode="adjust_rotate" app:aspectRatioMode="adjust"
app:AAEnabled="false" app:AAEnabled="false"
app:numFilters="1" app:numFilters="1"
app:isFlipHorizontal="false" app:isFlipHorizontal="false"

View File

@ -1,14 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.5.10' ext.kotlin_version = '1.6.21'
ext.rtsp_version_name = '2.1.9'
ext.rtsp_version_code = 219
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.2' classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5' classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.4.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -18,7 +20,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
} }

View File

@ -1,11 +1,13 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
android { android {
compileSdkVersion 30 compileSdkVersion 32
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 32
} }
buildTypes { buildTypes {
release { release {
@ -13,8 +15,11 @@ android {
consumerProguardFiles 'proguard-rules.pro' consumerProguardFiles 'proguard-rules.pro'
} }
} }
namespace 'com.pedro.encoder'
} }
dependencies { dependencies {
api 'androidx.annotation:annotation:1.2.0' api 'androidx.annotation:annotation:1.3.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
} }

View File

@ -1 +1 @@
<manifest package="com.pedro.encoder" /> <manifest />

View File

@ -1,37 +1,145 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder; package com.pedro.encoder;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.utils.CodecUtil; import com.pedro.encoder.utils.CodecUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/** /**
* Created by pedro on 18/09/19. * Created by pedro on 18/09/19.
*/ */
public abstract class BaseEncoder implements EncoderCallback { public abstract class BaseEncoder implements EncoderCallback {
private static final String TAG = "BaseEncoder"; protected String TAG = "BaseEncoder";
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); private final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
private HandlerThread handlerThread;
protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<>(80);
protected MediaCodec codec; protected MediaCodec codec;
protected long presentTimeUs; protected static long presentTimeUs;
protected volatile boolean running = false; protected volatile boolean running = false;
protected boolean isBufferMode = true; protected boolean isBufferMode = true;
protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND; protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND;
private MediaCodec.Callback callback;
private long oldTimeStamp = 0L;
protected boolean shouldReset = true;
public void restart() {
start(false);
initCodec();
}
public void start() { public void start() {
if (presentTimeUs == 0) {
presentTimeUs = System.nanoTime() / 1000;
}
start(true); start(true);
initCodec();
} }
private void initCodec() {
handlerThread = new HandlerThread(TAG);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
createAsyncCallback();
codec.setCallback(callback, handler);
codec.start();
} else {
codec.start();
handler.post(new Runnable() {
@Override
public void run() {
while (running) {
try {
getDataFromEncoder();
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
reloadCodec();
}
}
}
});
}
running = true;
}
public abstract void reset();
public abstract void start(boolean resetTs); public abstract void start(boolean resetTs);
protected abstract void stopImp(); protected abstract void stopImp();
protected void fixTimeStamp(MediaCodec.BufferInfo info) {
if (oldTimeStamp > info.presentationTimeUs) {
info.presentationTimeUs = oldTimeStamp;
} else {
oldTimeStamp = info.presentationTimeUs;
}
}
private void reloadCodec() {
//Sometimes encoder crash, we will try recover it. Reset encoder a time if crash
if (shouldReset) {
Log.e(TAG, "Encoder crashed, trying to recover it");
reset();
}
}
public void stop() { public void stop() {
stop(true);
}
public void stop(boolean resetTs) {
if (resetTs) {
presentTimeUs = 0;
}
running = false; running = false;
stopImp(); stopImp();
if (handlerThread != null) {
if (handlerThread.getLooper() != null) {
if (handlerThread.getLooper().getThread() != null) {
handlerThread.getLooper().getThread().interrupt();
}
handlerThread.getLooper().quit();
}
handlerThread.quit();
if (codec != null) {
try {
codec.flush();
} catch (IllegalStateException ignored) { }
}
//wait for thread to die for 500ms.
try {
handlerThread.getLooper().getThread().join(500);
} catch (Exception ignored) { }
}
queue.clear();
queue = new ArrayBlockingQueue<>(80);
try { try {
codec.stop(); codec.stop();
codec.release(); codec.release();
@ -39,18 +147,19 @@ public abstract class BaseEncoder implements EncoderCallback {
} catch (IllegalStateException | NullPointerException e) { } catch (IllegalStateException | NullPointerException e) {
codec = null; codec = null;
} }
oldTimeStamp = 0L;
} }
protected abstract MediaCodecInfo chooseEncoder(String mime); protected abstract MediaCodecInfo chooseEncoder(String mime);
protected void getDataFromEncoder(Frame frame) throws IllegalStateException { protected void getDataFromEncoder() throws IllegalStateException {
if (isBufferMode) { if (isBufferMode) {
int inBufferIndex = codec.dequeueInputBuffer(0); int inBufferIndex = codec.dequeueInputBuffer(0);
if (inBufferIndex >= 0) { if (inBufferIndex >= 0) {
inputAvailable(codec, inBufferIndex, frame); inputAvailable(codec, inBufferIndex);
} }
} }
for (; running; ) { while (running) {
int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0); int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat mediaFormat = codec.getOutputFormat(); MediaFormat mediaFormat = codec.getOutputFormat();
@ -65,16 +174,22 @@ public abstract class BaseEncoder implements EncoderCallback {
protected abstract Frame getInputFrame() throws InterruptedException; protected abstract Frame getInputFrame() throws InterruptedException;
protected abstract long calculatePts(Frame frame, long presentTimeUs);
private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec, private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
int inBufferIndex, Frame frame) throws IllegalStateException { int inBufferIndex) throws IllegalStateException {
try { try {
if (frame == null) frame = getInputFrame(); Frame frame = getInputFrame();
while (frame == null) frame = getInputFrame();
byteBuffer.clear(); byteBuffer.clear();
byteBuffer.put(frame.getBuffer(), frame.getOffset(), frame.getSize()); int size = Math.max(0, Math.min(frame.getSize(), byteBuffer.remaining()) - frame.getOffset());
long pts = System.nanoTime() / 1000 - presentTimeUs; byteBuffer.put(frame.getBuffer(), frame.getOffset(), size);
mediaCodec.queueInputBuffer(inBufferIndex, 0, frame.getSize(), pts, 0); long pts = calculatePts(frame, presentTimeUs);
mediaCodec.queueInputBuffer(inBufferIndex, 0, size, pts, 0);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (NullPointerException | IndexOutOfBoundsException e) {
Log.i(TAG, "Encoding error", e);
} }
} }
@ -100,7 +215,7 @@ public abstract class BaseEncoder implements EncoderCallback {
} }
@Override @Override
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame) public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
throws IllegalStateException { throws IllegalStateException {
ByteBuffer byteBuffer; ByteBuffer byteBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ -108,7 +223,7 @@ public abstract class BaseEncoder implements EncoderCallback {
} else { } else {
byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex]; byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
} }
processInput(byteBuffer, mediaCodec, inBufferIndex, frame); processInput(byteBuffer, mediaCodec, inBufferIndex);
} }
@Override @Override
@ -122,4 +237,41 @@ public abstract class BaseEncoder implements EncoderCallback {
} }
processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo); processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo);
} }
@RequiresApi(api = Build.VERSION_CODES.M)
private void createAsyncCallback() {
callback = new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex) {
try {
inputAvailable(mediaCodec, inBufferIndex);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
reloadCodec();
}
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
@NonNull MediaCodec.BufferInfo bufferInfo) {
try {
outputAvailable(mediaCodec, outBufferIndex, bufferInfo);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
reloadCodec();
}
}
@Override
public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
Log.e(TAG, "Error", e);
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec,
@NonNull MediaFormat mediaFormat) {
formatChanged(mediaCodec, mediaFormat);
}
};
}
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder; package com.pedro.encoder;
import android.media.MediaCodec; import android.media.MediaCodec;
@ -8,7 +24,7 @@ import androidx.annotation.NonNull;
* Created by pedro on 18/09/19. * Created by pedro on 18/09/19.
*/ */
public interface EncoderCallback { public interface EncoderCallback {
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame) void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
throws IllegalStateException; throws IllegalStateException;
void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex, void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder; package com.pedro.encoder;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder;
public interface GetFrame {
Frame getInputFrame();
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.audio; package com.pedro.encoder.audio;
import android.media.MediaCodec; import android.media.MediaCodec;
@ -7,11 +23,10 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.pedro.encoder.BaseEncoder; import com.pedro.encoder.BaseEncoder;
import com.pedro.encoder.Frame; import com.pedro.encoder.Frame;
import com.pedro.encoder.GetFrame;
import com.pedro.encoder.input.audio.GetMicrophoneData; import com.pedro.encoder.input.audio.GetMicrophoneData;
import com.pedro.encoder.utils.CodecUtil; import com.pedro.encoder.utils.CodecUtil;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -22,15 +37,18 @@ import java.util.List;
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData { public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
private static final String TAG = "AudioEncoder"; private final GetAacData getAacData;
private GetAacData getAacData;
private int bitRate = 64 * 1024; //in kbps private int bitRate = 64 * 1024; //in kbps
private int sampleRate = 32000; //in hz private int sampleRate = 32000; //in hz
private int maxInputSize = 0;
private boolean isStereo = true; private boolean isStereo = true;
private GetFrame getFrame;
private long bytesRead = 0;
private boolean tsModeBuffer = false;
public AudioEncoder(GetAacData getAacData) { public AudioEncoder(GetAacData getAacData) {
this.getAacData = getAacData; this.getAacData = getAacData;
TAG = "AudioEncoder";
} }
/** /**
@ -38,31 +56,19 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
*/ */
public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo, public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
int maxInputSize) { int maxInputSize) {
this.bitRate = bitRate;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.maxInputSize = maxInputSize;
this.isStereo = isStereo;
isBufferMode = true; isBufferMode = true;
try { try {
List<MediaCodecInfo> encoders = new ArrayList<>(); MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
if (force == CodecUtil.Force.HARDWARE) { if (encoder != null) {
encoders = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME); Log.i(TAG, "Encoder selected " + encoder.getName());
} else if (force == CodecUtil.Force.SOFTWARE) { codec = MediaCodec.createByCodecName(encoder.getName());
encoders = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
}
if (force == CodecUtil.Force.FIRST_COMPATIBLE_FOUND) {
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
if (encoder != null) {
codec = MediaCodec.createByCodecName(encoder.getName());
} else {
Log.e(TAG, "Valid encoder not found");
return false;
}
} else { } else {
if (encoders.isEmpty()) { Log.e(TAG, "Valid encoder not found");
Log.e(TAG, "Valid encoder not found"); return false;
return false;
} else {
codec = MediaCodec.createByCodecName(encoders.get(0).getName());
}
} }
int channelCount = (isStereo) ? 2 : 1; int channelCount = (isStereo) ? 2 : 1;
@ -76,41 +82,65 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
running = false; running = false;
Log.i(TAG, "prepared"); Log.i(TAG, "prepared");
return true; return true;
} catch (IOException | IllegalStateException e) { } catch (Exception e) {
Log.e(TAG, "Create AudioEncoder failed.", e); Log.e(TAG, "Create AudioEncoder failed.", e);
this.stop();
return false; return false;
} }
} }
public void setGetFrame(GetFrame getFrame) {
this.getFrame = getFrame;
}
/** /**
* Prepare encoder with default parameters * Prepare encoder with default parameters
*/ */
public boolean prepareAudioEncoder() { public boolean prepareAudioEncoder() {
return prepareAudioEncoder(bitRate, sampleRate, isStereo, 0); return prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
} }
@Override @Override
public void start(boolean resetTs) { public void start(boolean resetTs) {
presentTimeUs = System.nanoTime() / 1000; shouldReset = resetTs;
codec.start();
running = true;
Log.i(TAG, "started"); Log.i(TAG, "started");
} }
@Override @Override
protected void stopImp() { protected void stopImp() {
bytesRead = 0;
Log.i(TAG, "stopped"); Log.i(TAG, "stopped");
} }
@Override
public void reset() {
stop(false);
prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
restart();
}
@Override @Override
protected Frame getInputFrame() throws InterruptedException { protected Frame getInputFrame() throws InterruptedException {
return null; return getFrame != null ? getFrame.getInputFrame() : queue.take();
}
@Override
protected long calculatePts(Frame frame, long presentTimeUs) {
long pts;
if (tsModeBuffer) {
int channels = isStereo ? 2 : 1;
pts = 1000000 * bytesRead / 2 / channels / sampleRate;
bytesRead += frame.getSize();
} else {
pts = System.nanoTime() / 1000 - presentTimeUs;
}
return pts;
} }
@Override @Override
protected void checkBuffer(@NonNull ByteBuffer byteBuffer, protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
@NonNull MediaCodec.BufferInfo bufferInfo) { @NonNull MediaCodec.BufferInfo bufferInfo) {
fixTimeStamp(bufferInfo);
} }
@Override @Override
@ -123,39 +153,45 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
* Set custom PCM data. * Set custom PCM data.
* Use it after prepareAudioEncoder(int sampleRate, int channel). * Use it after prepareAudioEncoder(int sampleRate, int channel).
* Used too with microphone. * Used too with microphone.
*
*/ */
@Override @Override
public void inputPCMData(Frame frame) { public void inputPCMData(Frame frame) {
if (running) { if (running && !queue.offer(frame)) {
try {
getDataFromEncoder(frame);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
}
} else {
Log.i(TAG, "frame discarded"); Log.i(TAG, "frame discarded");
} }
} }
@Override @Override
protected MediaCodecInfo chooseEncoder(String mime) { protected MediaCodecInfo chooseEncoder(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = CodecUtil.getAllEncoders(mime); List<MediaCodecInfo> mediaCodecInfoList;
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { if (force == CodecUtil.Force.HARDWARE) {
String name = mediaCodecInfo.getName().toLowerCase(); mediaCodecInfoList = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
if (!name.contains("omx.google")) return mediaCodecInfo; } else if (force == CodecUtil.Force.SOFTWARE) {
} mediaCodecInfoList = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
if (mediaCodecInfoList.size() > 0) {
return mediaCodecInfoList.get(0);
} else { } else {
return null; //Priority: hardware > software
mediaCodecInfoList = CodecUtil.getAllEncoders(CodecUtil.AAC_MIME, true);
} }
Log.i(TAG, mediaCodecInfoList.size() + " encoders found");
if (mediaCodecInfoList.isEmpty()) return null;
else return mediaCodecInfoList.get(0);
} }
public void setSampleRate(int sampleRate) { public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
} }
public boolean isTsModeBuffer() {
return tsModeBuffer;
}
public void setTsModeBuffer(boolean tsModeBuffer) {
if (!isRunning()) {
this.tsModeBuffer = tsModeBuffer;
}
}
@Override @Override
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) { public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
getAacData.onAudioFormat(mediaFormat); getAacData.onAudioFormat(mediaFormat);

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.audio; package com.pedro.encoder.audio;
import android.media.MediaCodec; import android.media.MediaCodec;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio; package com.pedro.encoder.input.audio;
import android.media.audiofx.AcousticEchoCanceler; import android.media.audiofx.AcousticEchoCanceler;
@ -13,7 +29,7 @@ public class AudioPostProcessEffect {
private final String TAG = "AudioPostProcessEffect"; private final String TAG = "AudioPostProcessEffect";
private int microphoneId; private final int microphoneId;
private AcousticEchoCanceler acousticEchoCanceler = null; private AcousticEchoCanceler acousticEchoCanceler = null;
private AutomaticGainControl automaticGainControl = null; private AutomaticGainControl automaticGainControl = null;
private NoiseSuppressor noiseSuppressor = null; private NoiseSuppressor noiseSuppressor = null;
@ -25,11 +41,13 @@ public class AudioPostProcessEffect {
public void enableAutoGainControl() { public void enableAutoGainControl() {
if (AutomaticGainControl.isAvailable() && automaticGainControl == null) { if (AutomaticGainControl.isAvailable() && automaticGainControl == null) {
automaticGainControl = AutomaticGainControl.create(microphoneId); automaticGainControl = AutomaticGainControl.create(microphoneId);
automaticGainControl.setEnabled(true); if (automaticGainControl != null) {
Log.i(TAG, "AutoGainControl enabled"); automaticGainControl.setEnabled(true);
} else { Log.i(TAG, "AutoGainControl enabled");
Log.e(TAG, "This device don't support AutoGainControl"); return;
}
} }
Log.e(TAG, "This device doesn't implement AutoGainControl");
} }
public void releaseAutoGainControl() { public void releaseAutoGainControl() {
@ -43,11 +61,13 @@ public class AudioPostProcessEffect {
public void enableEchoCanceler() { public void enableEchoCanceler() {
if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) { if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) {
acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId); acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId);
acousticEchoCanceler.setEnabled(true); if (acousticEchoCanceler != null) {
Log.i(TAG, "EchoCanceler enabled"); acousticEchoCanceler.setEnabled(true);
} else { Log.i(TAG, "EchoCanceler enabled");
Log.e(TAG, "This device don't support EchoCanceler"); return;
}
} }
Log.e(TAG, "This device doesn't implement EchoCanceler");
} }
public void releaseEchoCanceler() { public void releaseEchoCanceler() {
@ -61,11 +81,13 @@ public class AudioPostProcessEffect {
public void enableNoiseSuppressor() { public void enableNoiseSuppressor() {
if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) { if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) {
noiseSuppressor = NoiseSuppressor.create(microphoneId); noiseSuppressor = NoiseSuppressor.create(microphoneId);
noiseSuppressor.setEnabled(true); if (noiseSuppressor != null) {
Log.i(TAG, "NoiseSuppressor enabled"); noiseSuppressor.setEnabled(true);
} else { Log.i(TAG, "NoiseSuppressor enabled");
Log.e(TAG, "This device don't support NoiseSuppressor"); return;
}
} }
Log.e(TAG, "This device doesn't implement NoiseSuppressor");
} }
public void releaseNoiseSuppressor() { public void releaseNoiseSuppressor() {
@ -75,4 +97,10 @@ public class AudioPostProcessEffect {
noiseSuppressor = null; noiseSuppressor = null;
} }
} }
public void release() {
releaseAutoGainControl();
releaseEchoCanceler();
releaseNoiseSuppressor();
}
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio; package com.pedro.encoder.input.audio;
public abstract class CustomAudioEffect { public abstract class CustomAudioEffect {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio; package com.pedro.encoder.input.audio;
import com.pedro.encoder.Frame; import com.pedro.encoder.Frame;

View File

@ -1,5 +1,22 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio; package com.pedro.encoder.input.audio;
import android.annotation.SuppressLint;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioPlaybackCaptureConfiguration; import android.media.AudioPlaybackCaptureConfiguration;
import android.media.AudioRecord; import android.media.AudioRecord;
@ -16,28 +33,29 @@ import java.nio.ByteBuffer;
* Created by pedro on 19/01/17. * Created by pedro on 19/01/17.
*/ */
@SuppressLint("MissingPermission")
public class MicrophoneManager { public class MicrophoneManager {
private final String TAG = "MicrophoneManager"; private final String TAG = "MicrophoneManager";
private static final int BUFFER_SIZE = 4096; private int BUFFER_SIZE = 0;
protected AudioRecord audioRecord; protected AudioRecord audioRecord;
private GetMicrophoneData getMicrophoneData; private final GetMicrophoneData getMicrophoneData;
private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); protected byte[] pcmBuffer = new byte[BUFFER_SIZE];
private byte[] pcmBufferMuted = new byte[BUFFER_SIZE]; protected byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
protected boolean running = false; protected boolean running = false;
private boolean created = false; private boolean created = false;
//default parameters for microphone //default parameters for microphone
private int sampleRate = 32000; //hz private int sampleRate = 32000; //hz
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT; private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int channel = AudioFormat.CHANNEL_IN_STEREO; private int channel = AudioFormat.CHANNEL_IN_STEREO;
private boolean muted = false; protected boolean muted = false;
private AudioPostProcessEffect audioPostProcessEffect; private AudioPostProcessEffect audioPostProcessEffect;
HandlerThread handlerThread; protected HandlerThread handlerThread;
private CustomAudioEffect customAudioEffect = new NoAudioEffect(); protected CustomAudioEffect customAudioEffect = new NoAudioEffect();
public MicrophoneManager(GetMicrophoneData getMicrophoneData) { public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
this.getMicrophoneData = getMicrophoneData; this.getMicrophoneData = getMicrophoneData;
getPcmBufferSize();
} }
public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) { public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) {
@ -55,61 +73,86 @@ public class MicrophoneManager {
/** /**
* Create audio record with params and default audio source * Create audio record with params and default audio source
*/ */
public void createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler, public boolean createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
boolean noiseSuppressor) { boolean noiseSuppressor) {
createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler, noiseSuppressor); return createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler,
noiseSuppressor);
} }
/** /**
* Create audio record with params and selected audio source * Create audio record with params and selected audio source
* @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the recording source definitions. *
* @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the
* recording source definitions.
*/ */
public void createMicrophone(int audioSource, int sampleRate, boolean isStereo, boolean echoCanceler, public boolean createMicrophone(int audioSource, int sampleRate, boolean isStereo,
boolean noiseSuppressor) { boolean echoCanceler, boolean noiseSuppressor) {
this.sampleRate = sampleRate; try {
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO; this.sampleRate = sampleRate;
audioRecord = channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
new AudioRecord(audioSource, sampleRate, channel, audioFormat, audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, getMaxInputSize() * 5);
getPcmBufferSize()); audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId()); if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler(); if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor(); String chl = (isStereo) ? "Stereo" : "Mono";
String chl = (isStereo) ? "Stereo" : "Mono"; if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl); throw new IllegalArgumentException("Some parameters specified is not valid");
created = true; }
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
created = true;
} catch (IllegalArgumentException e) {
Log.e(TAG, "create microphone error", e);
}
return created;
} }
/** /**
* Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal audio * Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling this! * audio
* * Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling
* @param config - AudioPlaybackCaptureConfiguration received from {@link android.media.projection.MediaProjection} * this!
* *
* @param config - AudioPlaybackCaptureConfiguration received from {@link
* android.media.projection.MediaProjection}
* @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection) * @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection)
* @see "https://developer.android.com/guide/topics/media/playback-capture" * @see "https://developer.android.com/guide/topics/media/playback-capture"
* @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9" * @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9"
*/ */
public void createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate, boolean isStereo) { public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) {
this.sampleRate = sampleRate; try {
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
audioRecord = new AudioRecord.Builder() this.sampleRate = sampleRate;
.setAudioPlaybackCaptureConfig(config) channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
.setAudioFormat(new AudioFormat.Builder() audioRecord = new AudioRecord.Builder().setAudioPlaybackCaptureConfig(config)
.setEncoding(audioFormat) .setAudioFormat(new AudioFormat.Builder().setEncoding(audioFormat)
.setSampleRate(sampleRate) .setSampleRate(sampleRate)
.setChannelMask(channel) .setChannelMask(channel)
.build()) .build())
.setBufferSizeInBytes(getPcmBufferSize()) .setBufferSizeInBytes(getMaxInputSize() * 5)
.build(); .build();
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId()); if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
String chl = (isStereo) ? "Stereo" : "Mono"; if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl); String chl = (isStereo) ? "Stereo" : "Mono";
created = true; if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
} else createMicrophone(sampleRate, isStereo, false, false); throw new IllegalArgumentException("Some parameters specified is not valid");
}
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
created = true;
} else {
return createMicrophone(sampleRate, isStereo, echoCanceler, noiseSuppressor);
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "create microphone error", e);
}
return created;
} }
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
boolean isStereo) {
return createInternalMicrophone(config, sampleRate, isStereo, false, false);
}
/** /**
* Start record and get data * Start record and get data
@ -126,8 +169,6 @@ public class MicrophoneManager {
Frame frame = read(); Frame frame = read();
if (frame != null) { if (frame != null) {
getMicrophoneData.inputPCMData(frame); getMicrophoneData.inputPCMData(frame);
} else {
running = false;
} }
} }
} }
@ -160,14 +201,10 @@ public class MicrophoneManager {
/** /**
* @return Object with size and PCM buffer data * @return Object with size and PCM buffer data
*/ */
private Frame read() { protected Frame read() {
pcmBuffer.rewind(); int size = audioRecord.read(pcmBuffer, 0, pcmBuffer.length);
int size = audioRecord.read(pcmBuffer, pcmBuffer.remaining()); if (size < 0) return null;
if (size <= 0) { return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer), 0, size);
return null;
}
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer.array()),
muted ? 0 : pcmBuffer.arrayOffset(), size);
} }
/** /**
@ -190,8 +227,7 @@ public class MicrophoneManager {
audioRecord = null; audioRecord = null;
} }
if (audioPostProcessEffect != null) { if (audioPostProcessEffect != null) {
audioPostProcessEffect.releaseEchoCanceler(); audioPostProcessEffect.release();
audioPostProcessEffect.releaseNoiseSuppressor();
} }
Log.i(TAG, "Microphone stopped"); Log.i(TAG, "Microphone stopped");
} }
@ -199,10 +235,10 @@ public class MicrophoneManager {
/** /**
* Get PCM buffer size * Get PCM buffer size
*/ */
private int getPcmBufferSize() { private void getPcmBufferSize() {
int pcmBufSize = BUFFER_SIZE = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
AudioRecord.getMinBufferSize(sampleRate, channel, AudioFormat.ENCODING_PCM_16BIT); pcmBuffer = new byte[BUFFER_SIZE];
return pcmBufSize * 5; pcmBufferMuted = new byte[BUFFER_SIZE];
} }
public int getMaxInputSize() { public int getMaxInputSize() {

View File

@ -1,15 +1,33 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio; package com.pedro.encoder.input.audio;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.util.Log; import android.util.Log;
import com.pedro.encoder.Frame;
import com.pedro.encoder.GetFrame;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Similar to MicrophoneManager but samples are not read automatically. * Similar to MicrophoneManager but samples are not read automatically.
* The owner must manually call read(...) as often as samples are needed. * The owner must manually call read(...) as often as samples are needed.
*/ */
public class MicrophoneManagerManual extends MicrophoneManager { public class MicrophoneManagerManual extends MicrophoneManager implements GetFrame {
private final String TAG = "MicMM"; private final String TAG = "MicMM";
@ -54,4 +72,13 @@ public class MicrophoneManagerManual extends MicrophoneManager {
handlerThread = new HandlerThread("nothing"); handlerThread = new HandlerThread("nothing");
super.stop(); super.stop();
} }
public GetFrame getGetFrame() {
return this;
}
@Override
public Frame getInputFrame() {
return read();
}
} }

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio;
public enum MicrophoneMode {
SYNC, ASYNC, BUFFER
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.audio; package com.pedro.encoder.input.audio;
public class NoAudioEffect extends CustomAudioEffect { public class NoAudioEffect extends CustomAudioEffect {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.decoder; package com.pedro.encoder.input.decoder;
import android.media.MediaCodec; import android.media.MediaCodec;
@ -6,160 +22,110 @@ import android.media.MediaFormat;
import android.util.Log; import android.util.Log;
import com.pedro.encoder.Frame; import com.pedro.encoder.Frame;
import com.pedro.encoder.input.audio.GetMicrophoneData; import com.pedro.encoder.input.audio.GetMicrophoneData;
import com.pedro.encoder.utils.CodecUtil;
import com.pedro.encoder.utils.PCMUtil; import com.pedro.encoder.utils.PCMUtil;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Created by pedro on 20/06/17. * Created by pedro on 20/06/17.
*/ */
public class AudioDecoder { public class AudioDecoder extends BaseDecoder {
private final String TAG = "AudioDecoder";
private AudioDecoderInterface audioDecoderInterface; private AudioDecoderInterface audioDecoderInterface;
private LoopFileInterface loopFileInterface;
private MediaExtractor audioExtractor;
private MediaCodec audioDecoder;
private MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();
private boolean decoding;
private Thread thread;
private GetMicrophoneData getMicrophoneData; private GetMicrophoneData getMicrophoneData;
private MediaFormat audioFormat;
private String mime = "";
private int sampleRate; private int sampleRate;
private boolean isStereo; private boolean isStereo;
private int channels = 1; private int channels = 1;
private int size = 2048; private int size = 2048;
private byte[] pcmBuffer = new byte[size]; private byte[] pcmBuffer = new byte[size];
private byte[] pcmBufferMuted = new byte[11]; private byte[] pcmBufferMuted = new byte[11];
private static boolean loopMode = false;
private boolean muted = false; private boolean muted = false;
private long duration;
private volatile long seekTime = 0;
private volatile long startMs = 0;
public AudioDecoder(GetMicrophoneData getMicrophoneData, public AudioDecoder(GetMicrophoneData getMicrophoneData,
AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) { AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) {
super(loopFileInterface);
TAG = "AudioDecoder";
this.getMicrophoneData = getMicrophoneData; this.getMicrophoneData = getMicrophoneData;
this.audioDecoderInterface = audioDecoderInterface; this.audioDecoderInterface = audioDecoderInterface;
this.loopFileInterface = loopFileInterface;
} }
public boolean initExtractor(String filePath) throws IOException { @Override
decoding = false; protected boolean extract(MediaExtractor audioExtractor) {
audioExtractor = new MediaExtractor(); size = 2048;
audioExtractor.setDataSource(filePath); running = false;
for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) { for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) {
audioFormat = audioExtractor.getTrackFormat(i); mediaFormat = audioExtractor.getTrackFormat(i);
mime = audioFormat.getString(MediaFormat.KEY_MIME); mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio/")) { if (mime.startsWith("audio/")) {
audioExtractor.selectTrack(i); audioExtractor.selectTrack(i);
} else { } else {
audioFormat = null; mediaFormat = null;
} }
} }
if (audioFormat != null) { if (mediaFormat != null) {
channels = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); channels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
isStereo = channels >= 2; isStereo = channels >= 2;
sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
duration = audioFormat.getLong(MediaFormat.KEY_DURATION); duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
if (channels >= 2) { fixBuffer();
pcmBuffer = new byte[2048 * channels];
}
return true; return true;
//audio decoder not supported //audio decoder not supported
} else { } else {
mime = ""; mime = "";
audioFormat = null; mediaFormat = null;
return false; return false;
} }
} }
private void fixBuffer() {
if (channels >= 2) {
size *= channels;
}
pcmBuffer = new byte[size];
}
public boolean prepareAudio() { public boolean prepareAudio() {
try { return prepare(null);
audioDecoder = MediaCodec.createDecoderByType(mime);
audioDecoder.configure(audioFormat, null, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
} }
public void start() { public void reset() {
decoding = true; resetCodec(null);
audioDecoder.start();
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
decodeAudio();
} catch (IllegalStateException e) {
Log.i(TAG, "Decoding error", e);
}
}
});
thread.start();
} }
public void stop() { @Override
decoding = false; protected void decode() {
seekTime = 0; ByteBuffer[] inputBuffers = codec.getInputBuffers();
if (thread != null) { ByteBuffer[] outputBuffers = codec.getOutputBuffers();
thread.interrupt();
try {
thread.join(100);
} catch (InterruptedException e) {
thread.interrupt();
}
thread = null;
}
try {
audioDecoder.stop();
audioDecoder.release();
audioDecoder = null;
} catch (IllegalStateException | NullPointerException e) {
audioDecoder = null;
}
if (audioExtractor != null) {
audioExtractor.release();
audioExtractor = null;
}
}
private void decodeAudio() throws IllegalStateException {
ByteBuffer[] inputBuffers = audioDecoder.getInputBuffers();
ByteBuffer[] outputBuffers = audioDecoder.getOutputBuffers();
startMs = System.currentTimeMillis(); startMs = System.currentTimeMillis();
while (decoding) { while (running) {
int inIndex = audioDecoder.dequeueInputBuffer(10000); int inIndex = codec.dequeueInputBuffer(10000);
if (inIndex >= 0) { if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex]; ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = audioExtractor.readSampleData(buffer, 0); int sampleSize = extractor.readSampleData(buffer, 0);
if (sampleSize < 0) { if (sampleSize < 0) {
audioDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else { } else {
audioDecoder.queueInputBuffer(inIndex, 0, sampleSize, audioExtractor.getSampleTime(), 0); codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
audioExtractor.advance(); extractor.advance();
} }
int outIndex = audioDecoder.dequeueOutputBuffer(audioInfo, 10000); int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
switch (outIndex) { switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = audioDecoder.getOutputBuffers(); outputBuffers = codec.getOutputBuffers();
break; break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
case MediaCodec.INFO_TRY_AGAIN_LATER: case MediaCodec.INFO_TRY_AGAIN_LATER:
break; break;
default: default:
//needed for fix decode speed long extractorTs = extractor.getSampleTime() / 1000;
while (audioExtractor.getSampleTime() / 1000 long currentTs = System.currentTimeMillis() - startMs + seekTime;
> System.currentTimeMillis() - startMs + seekTime) { if (extractorTs > currentTs) {
try { try {
Thread.sleep(10); long sleepTime = extractorTs - currentTs;
Thread.sleep(sleepTime);
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (thread != null) thread.interrupt(); Thread.currentThread().interrupt();
return; return;
} }
} }
@ -167,13 +133,13 @@ public class AudioDecoder {
//This buffer is PCM data //This buffer is PCM data
if (muted) { if (muted) {
outBuffer.get(pcmBufferMuted, 0, outBuffer.get(pcmBufferMuted, 0,
outBuffer.remaining() <= pcmBufferMuted.length ? outBuffer.remaining() Math.min(outBuffer.remaining(), pcmBufferMuted.length));
: pcmBufferMuted.length);
getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length)); getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length));
} else { } else {
outBuffer.get(pcmBuffer, 0, if (pcmBuffer.length < outBuffer.remaining()) {
outBuffer.remaining() <= pcmBuffer.length ? outBuffer.remaining() pcmBuffer = new byte[outBuffer.remaining()];
: pcmBuffer.length); }
outBuffer.get(pcmBuffer, 0, Math.min(outBuffer.remaining(), pcmBuffer.length));
if (channels > 2) { //downgrade to stereo if (channels > 2) { //downgrade to stereo
byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels); byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels);
getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length)); getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length));
@ -181,42 +147,54 @@ public class AudioDecoder {
getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length)); getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length));
} }
} }
audioDecoder.releaseOutputBuffer(outIndex, false); codec.releaseOutputBuffer(outIndex, false);
break; break;
} }
}
// All decoded frames have been rendered, we can stop playing now // All decoded frames have been rendered, we can stop playing now
if ((audioInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0; seekTime = 0;
Log.i(TAG, "end of file out"); Log.i(TAG, "end of file out");
if (loopMode) { running = false;
loopFileInterface.onReset(false); if (loopMode) {
} else { loopFileInterface.onReset(false);
audioDecoderInterface.onAudioDecoderFinished(); } else {
} audioDecoderInterface.onAudioDecoderFinished();
} }
} }
} }
} }
public double getTime() { /**
if (decoding) { * This method should be called after prepare.
return audioExtractor.getSampleTime() / 10E5; * Get max output size to set max input size in encoder.
*/
public int getOutsize() {
if (!(mime.equals(CodecUtil.AAC_MIME) || mime.equals(CodecUtil.OPUS_MIME) || mime.equals(
CodecUtil.VORBIS_MIME))) {
Log.i(TAG, "fixing input size");
try {
if (running) {
return codec.getOutputBuffers()[0].remaining();
} else {
if (codec != null) {
codec.start();
int outSize = codec.getOutputBuffers()[0].remaining();
stopDecoder();
if (prepare(null)) return outSize;
}
return 0;
}
} catch (Exception e) {
return 0;
}
} else { } else {
Log.i(TAG, "default input size");
return 0; return 0;
} }
} }
public void moveTo(double time) {
audioExtractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
seekTime = audioExtractor.getSampleTime() / 1000;
startMs = System.currentTimeMillis();
}
public void setLoopMode(boolean loopMode) {
this.loopMode = loopMode;
}
public void mute() { public void mute() {
muted = true; muted = true;
} }
@ -236,8 +214,4 @@ public class AudioDecoder {
public boolean isStereo() { public boolean isStereo() {
return isStereo; return isStereo;
} }
public double getDuration() {
return duration / 10E5;
}
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.decoder; package com.pedro.encoder.input.decoder;
/** /**

View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.decoder;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaCodec;
import android.media.MediaDataSource;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.Surface;
import androidx.annotation.RequiresApi;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
public abstract class BaseDecoder {
protected static String TAG = "BaseDecoder";
protected LoopFileInterface loopFileInterface;
protected MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
protected MediaExtractor extractor;
protected MediaCodec codec;
protected volatile boolean running = false;
protected MediaFormat mediaFormat;
private HandlerThread handlerThread;
protected String mime = "";
protected boolean loopMode = false;
protected volatile long seekTime = 0;
protected volatile long startMs = 0;
protected long duration;
public BaseDecoder(LoopFileInterface loopFileInterface) {
this.loopFileInterface = loopFileInterface;
}
public boolean initExtractor(String filePath) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(filePath);
return extract(extractor);
}
public boolean initExtractor(FileDescriptor fileDescriptor) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(fileDescriptor);
return extract(extractor);
}
@RequiresApi(api = Build.VERSION_CODES.N)
public boolean initExtractor(AssetFileDescriptor assetFileDescriptor) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(assetFileDescriptor);
return extract(extractor);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public boolean initExtractor(MediaDataSource mediaDataSource) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(mediaDataSource);
return extract(extractor);
}
public boolean initExtractor(String filePath, Map<String, String> headers) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(filePath, headers);
return extract(extractor);
}
public boolean initExtractor(FileDescriptor fileDescriptor, long offset, long length)
throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(fileDescriptor, offset, length);
return extract(extractor);
}
public boolean initExtractor(Context context, Uri uri, Map<String, String> headers)
throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(context, uri, headers);
return extract(extractor);
}
public void start() {
Log.i(TAG, "start decoder");
running = true;
handlerThread = new HandlerThread(TAG);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
codec.start();
handler.post(new Runnable() {
@Override
public void run() {
try {
decode();
} catch (IllegalStateException e) {
Log.i(TAG, "Decoding error", e);
} catch (NullPointerException e) {
Log.i(TAG, "Decoder maybe was stopped");
Log.i(TAG, "Decoding error", e);
}
}
});
}
public void stop() {
Log.i(TAG, "stop decoder");
running = false;
stopDecoder();
if (extractor != null) {
extractor.release();
extractor = null;
mime = "";
}
}
protected boolean prepare(Surface surface) {
try {
codec = MediaCodec.createDecoderByType(mime);
codec.configure(mediaFormat, surface, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
}
protected void resetCodec(Surface surface) {
boolean wasRunning = running;
stopDecoder();
if (extractor != null) seekTime = extractor.getSampleTime() / 1000;
prepare(surface);
if (wasRunning) {
start();
}
}
protected void stopDecoder() {
running = false;
seekTime = 0;
if (handlerThread != null) {
if (handlerThread.getLooper() != null) {
if (handlerThread.getLooper().getThread() != null) {
handlerThread.getLooper().getThread().interrupt();
}
handlerThread.getLooper().quit();
}
handlerThread.quit();
if (codec != null) {
try {
codec.flush();
} catch (IllegalStateException ignored) { }
}
//wait for thread to die for 500ms.
try {
handlerThread.getLooper().getThread().join(500);
} catch (Exception ignored) { }
handlerThread = null;
}
try {
codec.stop();
codec.release();
codec = null;
} catch (IllegalStateException | NullPointerException e) {
codec = null;
}
}
public void moveTo(double time) {
extractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
seekTime = extractor.getSampleTime() / 1000;
startMs = System.currentTimeMillis();
}
public void setLoopMode(boolean loopMode) {
this.loopMode = loopMode;
}
public boolean isLoopMode() {
return loopMode;
}
public double getDuration() {
return duration / 10E5;
}
public double getTime() {
if (running) {
return extractor.getSampleTime() / 10E5;
} else {
return 0;
}
}
protected abstract boolean extract(MediaExtractor extractor);
protected abstract void decode();
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.decoder; package com.pedro.encoder.input.decoder;
/** /**

View File

@ -1,178 +1,130 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.decoder; package com.pedro.encoder.input.decoder;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaExtractor; import android.media.MediaExtractor;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Build;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Created by pedro on 20/06/17. * Created by pedro on 20/06/17.
*/ */
public class VideoDecoder { public class VideoDecoder extends BaseDecoder {
private final String TAG = "VideoDecoder";
private VideoDecoderInterface videoDecoderInterface; private VideoDecoderInterface videoDecoderInterface;
private LoopFileInterface loopFileInterface;
private MediaExtractor videoExtractor;
private MediaCodec videoDecoder;
private MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();
private boolean decoding;
private Thread thread;
private MediaFormat videoFormat;
private String mime = "";
private int width; private int width;
private int height; private int height;
private long duration;
private static boolean loopMode = false;
private volatile long seekTime = 0;
private volatile long startMs = 0;
public VideoDecoder(VideoDecoderInterface videoDecoderInterface, public VideoDecoder(VideoDecoderInterface videoDecoderInterface,
LoopFileInterface loopFileInterface) { LoopFileInterface loopFileInterface) {
super(loopFileInterface);
TAG = "VideoDecoder";
this.videoDecoderInterface = videoDecoderInterface; this.videoDecoderInterface = videoDecoderInterface;
this.loopFileInterface = loopFileInterface;
} }
public boolean initExtractor(String filePath) throws IOException { @Override
decoding = false; protected boolean extract(MediaExtractor videoExtractor) {
videoExtractor = new MediaExtractor(); running = false;
videoExtractor.setDataSource(filePath);
for (int i = 0; i < videoExtractor.getTrackCount() && !mime.startsWith("video/"); i++) { for (int i = 0; i < videoExtractor.getTrackCount() && !mime.startsWith("video/"); i++) {
videoFormat = videoExtractor.getTrackFormat(i); mediaFormat = videoExtractor.getTrackFormat(i);
mime = videoFormat.getString(MediaFormat.KEY_MIME); mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) { if (mime.startsWith("video/")) {
videoExtractor.selectTrack(i); videoExtractor.selectTrack(i);
} else { } else {
videoFormat = null; mediaFormat = null;
} }
} }
if (videoFormat != null) { if (mediaFormat != null) {
width = videoFormat.getInteger(MediaFormat.KEY_WIDTH); width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
height = videoFormat.getInteger(MediaFormat.KEY_HEIGHT); height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
duration = videoFormat.getLong(MediaFormat.KEY_DURATION); duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
return true; return true;
//video decoder not supported //video decoder not supported
} else { } else {
mime = ""; mime = "";
videoFormat = null; mediaFormat = null;
return false; return false;
} }
} }
public boolean prepareVideo(Surface surface) { public boolean prepareVideo(Surface surface) {
try { return prepare(surface);
videoDecoder = MediaCodec.createDecoderByType(mime);
videoDecoder.configure(videoFormat, surface, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
} }
public void start() { public void reset(Surface surface) {
decoding = true; resetCodec(surface);
videoDecoder.start();
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
decodeVideo();
} catch (IllegalStateException e) {
Log.i(TAG, "Decoding error", e);
}
}
});
thread.start();
} }
public void stop() { @Override
decoding = false; protected void decode() {
seekTime = 0; ByteBuffer[] inputBuffers = codec.getInputBuffers();
if (thread != null) {
thread.interrupt();
try {
thread.join(100);
} catch (InterruptedException e) {
thread.interrupt();
}
thread = null;
}
try {
videoDecoder.stop();
videoDecoder.release();
videoDecoder = null;
} catch (IllegalStateException | NullPointerException e) {
videoDecoder = null;
}
if (videoExtractor != null) {
videoExtractor.release();
videoExtractor = null;
}
}
private void decodeVideo() throws IllegalStateException {
ByteBuffer[] inputBuffers = videoDecoder.getInputBuffers();
startMs = System.currentTimeMillis(); startMs = System.currentTimeMillis();
while (decoding) { while (running) {
int inIndex = videoDecoder.dequeueInputBuffer(10000); int inIndex = codec.dequeueInputBuffer(10000);
if (inIndex >= 0) { if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex]; ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = videoExtractor.readSampleData(buffer, 0); int sampleSize = extractor.readSampleData(buffer, 0);
if (sampleSize < 0) { if (sampleSize < 0) {
videoDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else { } else {
videoDecoder.queueInputBuffer(inIndex, 0, sampleSize, videoExtractor.getSampleTime(), 0); codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
videoExtractor.advance(); extractor.advance();
} }
} }
int outIndex = videoDecoder.dequeueOutputBuffer(videoInfo, 10000); int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
if (outIndex >= 0) { if (outIndex >= 0) {
while (videoExtractor.getSampleTime() / 1000 long extractorTs = extractor.getSampleTime() / 1000;
> System.currentTimeMillis() - startMs + seekTime) { long currentTs = System.currentTimeMillis() - startMs + seekTime;
if (extractorTs > currentTs) {
try { try {
Thread.sleep(10); long sleepTime = extractorTs - currentTs;
Thread.sleep(sleepTime);
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (thread != null) thread.interrupt(); Thread.currentThread().interrupt();
return; return;
} }
} }
videoDecoder.releaseOutputBuffer(outIndex, videoInfo.size != 0); codec.releaseOutputBuffer(outIndex, bufferInfo.size != 0);
} }
if ((videoInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { }
seekTime = 0; if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.i(TAG, "end of file out"); seekTime = 0;
if (loopMode) { Log.i(TAG, "end of file out");
loopFileInterface.onReset(true); running = false;
} else { if (loopMode) {
videoDecoderInterface.onVideoDecoderFinished(); loopFileInterface.onReset(true);
} } else {
videoDecoderInterface.onVideoDecoderFinished();
} }
} }
} }
public double getTime() { public void changeOutputSurface(Surface surface) {
if (decoding) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return videoExtractor.getSampleTime() / 10E5; codec.setOutputSurface(surface);
} else { } else {
return 0; reset(surface);
} }
} }
public void moveTo(double time) {
videoExtractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
seekTime = videoExtractor.getSampleTime() / 1000;
startMs = System.currentTimeMillis();
}
public void setLoopMode(boolean loopMode) {
this.loopMode = loopMode;
}
public int getWidth() { public int getWidth() {
return width; return width;
} }
@ -180,8 +132,4 @@ public class VideoDecoder {
public int getHeight() { public int getHeight() {
return height; return height;
} }
public double getDuration() {
return duration / 10E5;
}
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.decoder; package com.pedro.encoder.input.decoder;
/** /**

View File

@ -0,0 +1,157 @@
package com.pedro.encoder.input.gl;
import android.graphics.PointF;
import android.view.View;
import com.pedro.encoder.utils.gl.TranslateTo;
/**
* Created by pedro on 3/2/22.
*/
public class AndroidViewSprite {
private PointF scale;
private PointF position;
private PointF rotationAxis;
private int rotation;
private View view;
public AndroidViewSprite() {
reset();
}
public void setView(View view) {
this.view = view;
rotationAxis = new PointF(view.getMeasuredWidth() / 2f, view.getMeasuredHeight() / 2f);
}
/**
* @param deltaX Position x in percent
* @param deltaY Position x in percent
*/
public void translate(float deltaX, float deltaY) {
position.x = deltaX;
position.y = deltaY;
}
/**
* @param translation Predefined position
*/
public void translate(TranslateTo translation) {
switch (translation) {
case CENTER:
this.position.x = 50f - scale.x / 2f;
this.position.y = 50f - scale.x / 2f;
break;
case BOTTOM:
this.position.x = 50f - scale.x / 2f;
this.position.y = 100f - scale.y;
break;
case TOP:
this.position.x = 50f - scale.x / 2f;
this.position.y = 0f;
break;
case LEFT:
this.position.x = 0f;
this.position.y = 50f - scale.y / 2f;
break;
case RIGHT:
this.position.x = 100f - scale.x;
this.position.y = 50f - scale.y / 2f;
break;
case TOP_LEFT:
this.position.x = 0f;
this.position.y = 0f;
break;
case TOP_RIGHT:
this.position.x = 100f - scale.x;
this.position.y = 0f;
break;
case BOTTOM_LEFT:
this.position.x = 0f;
this.position.y = 100f - scale.y;
break;
case BOTTOM_RIGHT:
this.position.x = 100f - scale.x;
this.position.y = 100f - scale.y;
break;
default:
break;
}
}
/**
* @param deltaX Scale x in percent
* @param deltaY Scale y in percent
*/
public void scale(float deltaX, float deltaY) {
//keep old position
position.x /= deltaX / scale.x;
position.y /= deltaY / scale.y;
//set new scale.
scale = new PointF(deltaX, deltaY);
}
/**
* @return Scale in percent
*/
public PointF getScale() {
return scale;
}
/**
* @return Position in percent
*/
public PointF getTranslation() {
return position;
}
public int getRotation() {
return rotation;
}
public PointF getRotationAxis() {
return rotationAxis;
}
public void setRotation(int rotation) {
if (rotation < 0) {
this.rotation = 0;
} else if (rotation > 360) {
this.rotation = 360;
} else {
this.rotation = rotation;
}
}
public void reset() {
scale = new PointF(0f, 0f);
position = new PointF(0f, 0f);
}
/**
* Traduce position in percent to work with canvas.
*/
public PointF getCanvasPosition(float previewX, float previewY) {
return new PointF(previewX * position.x / 100f, previewY * position.y / 100f);
}
/**
* Traduce scale in percent to work with canvas.
*/
public PointF getCanvasScale(float previewX, float previewY) {
float scaleFactorX = 100f * (float) view.getWidth() / previewX;
float scaleFactorY = 100f * (float) view.getHeight() / previewY;
return new PointF(scale.x / scaleFactorX, scale.y / scaleFactorY);
}
/**
* Calculate default scale if none is indicated after load the filter.
*/
public void calculateDefaultScale(float previewX, float previewY) {
if (scale.x == 0f && scale.y == 0f) {
scale.x = 100f * (float) view.getWidth() / previewX;
scale.y = 100f * (float) view.getHeight() / previewY;
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl;
/**
* Created by pedro on 21/9/21.
*/
public enum FilterAction {
SET, SET_INDEX, ADD, ADD_INDEX, CLEAR, REMOVE, REMOVE_INDEX
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl; package com.pedro.encoder.input.gl;
import android.graphics.PointF; import android.graphics.PointF;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl; package com.pedro.encoder.input.gl;
import android.graphics.PointF; import android.graphics.PointF;
@ -5,6 +21,8 @@ import android.os.Build;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import com.pedro.encoder.input.gl.render.filters.AndroidViewFilterRender;
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
import com.pedro.encoder.input.gl.render.filters.object.BaseObjectFilterRender; import com.pedro.encoder.input.gl.render.filters.object.BaseObjectFilterRender;
import com.pedro.encoder.input.video.CameraHelper; import com.pedro.encoder.input.video.CameraHelper;
@ -16,6 +34,7 @@ import com.pedro.encoder.input.video.CameraHelper;
public class SpriteGestureController { public class SpriteGestureController {
private BaseObjectFilterRender baseObjectFilterRender; private BaseObjectFilterRender baseObjectFilterRender;
private AndroidViewFilterRender androidViewFilterRender;
private float lastDistance; private float lastDistance;
private boolean preventMoveOutside = true; private boolean preventMoveOutside = true;
@ -26,12 +45,27 @@ public class SpriteGestureController {
this.baseObjectFilterRender = sprite; this.baseObjectFilterRender = sprite;
} }
public BaseObjectFilterRender getBaseObjectFilterRender() { public SpriteGestureController(AndroidViewFilterRender sprite) {
return baseObjectFilterRender; this.androidViewFilterRender = sprite;
}
public BaseFilterRender getFilterRender() {
return androidViewFilterRender == null ? baseObjectFilterRender : androidViewFilterRender;
} }
public void setBaseObjectFilterRender(BaseObjectFilterRender baseObjectFilterRender) { public void setBaseObjectFilterRender(BaseObjectFilterRender baseObjectFilterRender) {
this.baseObjectFilterRender = baseObjectFilterRender; this.baseObjectFilterRender = baseObjectFilterRender;
this.androidViewFilterRender = null;
}
public void setBaseObjectFilterRender(AndroidViewFilterRender androidViewFilterRender) {
this.androidViewFilterRender = androidViewFilterRender;
this.baseObjectFilterRender = null;
}
public void stopListener() {
this.androidViewFilterRender = null;
this.baseObjectFilterRender = null;
} }
public void setPreventMoveOutside(boolean preventMoveOutside) { public void setPreventMoveOutside(boolean preventMoveOutside) {
@ -39,22 +73,34 @@ public class SpriteGestureController {
} }
public boolean spriteTouched(View view, MotionEvent motionEvent) { public boolean spriteTouched(View view, MotionEvent motionEvent) {
if (baseObjectFilterRender == null) return false; if (baseObjectFilterRender == null && androidViewFilterRender == null) return false;
float xPercent = motionEvent.getX() * 100 / view.getWidth(); float xPercent = motionEvent.getX() * 100 / view.getWidth();
float yPercent = motionEvent.getY() * 100 / view.getHeight(); float yPercent = motionEvent.getY() * 100 / view.getHeight();
PointF scale = baseObjectFilterRender.getScale(); PointF scale;
PointF position = baseObjectFilterRender.getPosition(); PointF position;
if (baseObjectFilterRender != null) {
scale = baseObjectFilterRender.getScale();
position = baseObjectFilterRender.getPosition();
} else {
scale = androidViewFilterRender.getScale();
position = androidViewFilterRender.getPosition();
}
boolean xTouched = xPercent >= position.x && xPercent <= position.x + scale.x; boolean xTouched = xPercent >= position.x && xPercent <= position.x + scale.x;
boolean yTouched = yPercent >= position.y && yPercent <= position.y + scale.y; boolean yTouched = yPercent >= position.y && yPercent <= position.y + scale.y;
return xTouched && yTouched; return xTouched && yTouched;
} }
public void moveSprite(View view, MotionEvent motionEvent) { public void moveSprite(View view, MotionEvent motionEvent) {
if (baseObjectFilterRender == null) return; if (baseObjectFilterRender == null && androidViewFilterRender == null) return;
if (motionEvent.getPointerCount() == 1) { if (motionEvent.getPointerCount() == 1) {
float xPercent = motionEvent.getX() * 100 / view.getWidth(); float xPercent = motionEvent.getX() * 100 / view.getWidth();
float yPercent = motionEvent.getY() * 100 / view.getHeight(); float yPercent = motionEvent.getY() * 100 / view.getHeight();
PointF scale = baseObjectFilterRender.getScale(); PointF scale;
if (baseObjectFilterRender != null) {
scale = baseObjectFilterRender.getScale();
} else {
scale = androidViewFilterRender.getScale();
}
if (preventMoveOutside) { if (preventMoveOutside) {
float x = xPercent - scale.x / 2.0F; float x = xPercent - scale.x / 2.0F;
float y = yPercent - scale.y / 2.0F; float y = yPercent - scale.y / 2.0F;
@ -70,22 +116,39 @@ public class SpriteGestureController {
if (y + scale.y > 100.0F) { if (y + scale.y > 100.0F) {
y = 100.0F - scale.y; y = 100.0F - scale.y;
} }
baseObjectFilterRender.setPosition(x, y); if (baseObjectFilterRender != null) {
baseObjectFilterRender.setPosition(x, y);
} else {
androidViewFilterRender.setPosition(x, y);
}
} else { } else {
baseObjectFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f); if (baseObjectFilterRender != null) {
baseObjectFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
} else {
androidViewFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
}
} }
} }
} }
public void scaleSprite(MotionEvent motionEvent) { public void scaleSprite(MotionEvent motionEvent) {
if (baseObjectFilterRender == null) return; if (baseObjectFilterRender == null && androidViewFilterRender == null) return;
if (motionEvent.getPointerCount() > 1) { if (motionEvent.getPointerCount() > 1) {
float distance = CameraHelper.getFingerSpacing(motionEvent); float distance = CameraHelper.getFingerSpacing(motionEvent);
float percent = distance >= lastDistance ? 1 : -1; float percent = distance >= lastDistance ? 1 : -1;
PointF scale = baseObjectFilterRender.getScale(); PointF scale;
if (baseObjectFilterRender != null) {
scale = baseObjectFilterRender.getScale();
} else {
scale = androidViewFilterRender.getScale();
}
scale.x += percent; scale.x += percent;
scale.y += percent; scale.y += percent;
baseObjectFilterRender.setScale(scale.x, scale.y); if (baseObjectFilterRender != null) {
baseObjectFilterRender.setScale(scale.x, scale.y);
} else {
androidViewFilterRender.setScale(scale.x, scale.y);
}
lastDistance = distance; lastDistance = distance;
} }
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl; package com.pedro.encoder.input.gl;
import android.opengl.EGL14; import android.opengl.EGL14;
@ -19,45 +35,29 @@ import com.pedro.encoder.utils.gl.GlUtil;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SurfaceManager { public class SurfaceManager {
private static final String TAG = "SurfaceManager";
private static final int EGL_RECORDABLE_ANDROID = 0x3142; private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private EGLContext eglContext = null; private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
private EGLSurface eglSurface = null; private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
private EGLDisplay eglDisplay = null; private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
private volatile boolean isReady = false;
/** public boolean isReady() {
* Creates an EGL context and an EGL surface. return isReady;
*/
public SurfaceManager(Surface surface, SurfaceManager manager) {
eglSetup(surface, manager.eglContext);
}
/**
* Creates an EGL context and an EGL surface.
*/
public SurfaceManager(Surface surface, EGLContext eglContext) {
eglSetup(surface, eglContext);
}
/**
* Creates an EGL context and an EGL surface.
*/
public SurfaceManager(Surface surface) {
eglSetup(surface, null);
}
public SurfaceManager() {
eglSetup(null, null);
} }
public void makeCurrent() { public void makeCurrent() {
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
Log.e("Error", "eglMakeCurrent failed"); Log.e(TAG, "eglMakeCurrent failed");
} }
} }
public void swapBuffer() { public void swapBuffer() {
EGL14.eglSwapBuffers(eglDisplay, eglSurface); if (!EGL14.eglSwapBuffers(eglDisplay, eglSurface)) {
Log.e(TAG, "eglSwapBuffers failed");
}
} }
/** /**
@ -71,7 +71,11 @@ public class SurfaceManager {
/** /**
* Prepares EGL. We want a GLES 2.0 context and a surface that supports recording. * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
*/ */
private void eglSetup(Surface surface, EGLContext eglSharedContext) { public void eglSetup(int width, int height, Surface surface, EGLContext eglSharedContext) {
if (isReady) {
Log.e(TAG, "already ready, ignored");
return;
}
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL14.EGL_NO_DISPLAY) { if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display"); throw new RuntimeException("unable to get EGL14 display");
@ -83,23 +87,43 @@ public class SurfaceManager {
// Configure EGL for recording and OpenGL ES 2.0. // Configure EGL for recording and OpenGL ES 2.0.
int[] attribList; int[] attribList;
if (eglSharedContext == null) { if (eglSharedContext == null && surface == null) {
attribList = new int[]{
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
};
} else if (eglSharedContext == null) {
attribList = new int[]{
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
};
} else if (surface == null) {
attribList = new int[] { attribList = new int[] {
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */ EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */, /* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */ //EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
EGL14.EGL_NONE //EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
}; };
} else { } else {
attribList = new int[] { attribList = new int[] {
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1, EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */ /* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */, //EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */ //EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE EGL14.EGL_NONE
}; };
} }
EGLConfig[] configs = new EGLConfig[1]; EGLConfig[] configs = new EGLConfig[1];
@ -119,7 +143,7 @@ public class SurfaceManager {
// Create a window surface, and attach it to the Surface we received. // Create a window surface, and attach it to the Surface we received.
if (surface == null) { if (surface == null) {
int[] surfaceAttribs = { int[] surfaceAttribs = {
EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE
}; };
eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs, 0); eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs, 0);
} else { } else {
@ -129,6 +153,28 @@ public class SurfaceManager {
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0); eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0);
} }
GlUtil.checkEglError("eglCreateWindowSurface"); GlUtil.checkEglError("eglCreateWindowSurface");
isReady = true;
Log.i(TAG, "GL initialized");
}
public void eglSetup(Surface surface, SurfaceManager manager) {
eglSetup(2, 2, surface, manager.eglContext);
}
public void eglSetup(int width, int height, SurfaceManager manager) {
eglSetup(width, height, null, manager.eglContext);
}
public void eglSetup(Surface surface, EGLContext eglContext) {
eglSetup(2, 2, surface, eglContext);
}
public void eglSetup(Surface surface) {
eglSetup(2, 2, surface, null);
}
public void eglSetup() {
eglSetup(2, 2, null, null);
} }
/** /**
@ -142,10 +188,14 @@ public class SurfaceManager {
EGL14.eglDestroyContext(eglDisplay, eglContext); EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglReleaseThread(); EGL14.eglReleaseThread();
EGL14.eglTerminate(eglDisplay); EGL14.eglTerminate(eglDisplay);
Log.i(TAG, "GL released");
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglSurface = EGL14.EGL_NO_SURFACE;
isReady = false;
} else {
Log.e(TAG, "GL already released");
} }
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglSurface = EGL14.EGL_NO_SURFACE;
} }
public EGLContext getEglContext() { public EGLContext getEglContext() {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl; package com.pedro.encoder.input.gl;
import android.graphics.Bitmap; import android.graphics.Bitmap;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render; package com.pedro.encoder.input.gl.render;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render; package com.pedro.encoder.input.gl.render;
import android.content.Context; import android.content.Context;
@ -63,7 +79,7 @@ public class CameraRender extends BaseRenderOffScreen {
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix"); uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
//camera texture //camera texture
GlUtil.createExternalTextures(1, textureID, 0); GlUtil.createExternalTextures(textureID.length, textureID, 0);
surfaceTexture = new SurfaceTexture(textureID[0]); surfaceTexture = new SurfaceTexture(textureID[0]);
surfaceTexture.setDefaultBufferSize(width, height); surfaceTexture.setDefaultBufferSize(width, height);
surface = new Surface(surfaceTexture); surface = new Surface(surfaceTexture);
@ -107,8 +123,8 @@ public class CameraRender extends BaseRenderOffScreen {
@Override @Override
public void release() { public void release() {
GLES20.glDeleteProgram(program); GLES20.glDeleteProgram(program);
surfaceTexture = null; surfaceTexture.release();
surface = null; surface.release();
} }
public void updateTexImage() { public void updateTexImage() {

View File

@ -0,0 +1,177 @@
package com.pedro.encoder.input.gl.render
import android.content.Context
import android.graphics.SurfaceTexture
import android.os.Build
import android.view.Surface
import androidx.annotation.RequiresApi
import com.pedro.encoder.input.gl.FilterAction
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender
/**
* Created by pedro on 20/3/22.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class MainRender {
private val cameraRender = CameraRender()
private val screenRender = ScreenRender()
private var width = 0
private var height = 0
private var previewWidth = 0
private var previewHeight = 0
private var context: Context? = null
private var filterRenders: MutableList<BaseFilterRender> = ArrayList()
fun initGl(context: Context, encoderWidth: Int, encoderHeight: Int, previewWidth: Int, previewHeight: Int) {
this.context = context
width = encoderWidth
height = encoderHeight
this.previewWidth = previewWidth
this.previewHeight = previewHeight
cameraRender.initGl(width, height, context, previewWidth, previewHeight)
screenRender.setStreamSize(encoderWidth, encoderHeight)
screenRender.setTexId(cameraRender.texId)
screenRender.initGl(context)
}
fun drawOffScreen() {
cameraRender.draw()
for (baseFilterRender in filterRenders) baseFilterRender.draw()
}
fun drawScreen(width: Int, height: Int, keepAspectRatio: Boolean, mode: Int, rotation: Int,
flipStreamVertical: Boolean, flipStreamHorizontal: Boolean) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, flipStreamVertical,
flipStreamHorizontal)
}
fun drawScreenEncoder(width: Int, height: Int, isPortrait: Boolean, rotation: Int,
flipStreamVertical: Boolean, flipStreamHorizontal: Boolean) {
screenRender.drawEncoder(width, height, isPortrait, rotation, flipStreamVertical,
flipStreamHorizontal)
}
fun drawScreenPreview(width: Int, height: Int, isPortrait: Boolean, keepAspectRatio: Boolean,
mode: Int, rotation: Int, flipStreamVertical: Boolean, flipStreamHorizontal: Boolean) {
screenRender.drawPreview(width, height, isPortrait, keepAspectRatio, mode, rotation,
flipStreamVertical, flipStreamHorizontal)
}
fun release() {
cameraRender.release()
for (baseFilterRender in filterRenders) baseFilterRender.release()
filterRenders.clear()
screenRender.release()
}
private fun setFilter(position: Int, baseFilterRender: BaseFilterRender) {
val id = filterRenders[position].previousTexId
val renderHandler = filterRenders[position].renderHandler
filterRenders[position].release()
filterRenders[position] = baseFilterRender
filterRenders[position].previousTexId = id
filterRenders[position].initGl(width, height, context, previewWidth, previewHeight)
filterRenders[position].renderHandler = renderHandler
}
private fun addFilter(baseFilterRender: BaseFilterRender) {
filterRenders.add(baseFilterRender)
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight)
baseFilterRender.initFBOLink()
reOrderFilters()
}
private fun addFilter(position: Int, baseFilterRender: BaseFilterRender) {
filterRenders.add(position, baseFilterRender)
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight)
baseFilterRender.initFBOLink()
reOrderFilters()
}
private fun clearFilters() {
for (baseFilterRender in filterRenders) {
baseFilterRender.release()
}
filterRenders.clear()
reOrderFilters()
}
private fun removeFilter(position: Int) {
filterRenders.removeAt(position).release()
reOrderFilters()
}
private fun removeFilter(baseFilterRender: BaseFilterRender) {
baseFilterRender.release()
filterRenders.remove(baseFilterRender)
reOrderFilters()
}
private fun reOrderFilters() {
for (i in filterRenders.indices) {
val texId = if (i == 0) cameraRender.texId else filterRenders[i - 1].texId
filterRenders[i].previousTexId = texId
}
val texId = if (filterRenders.isEmpty()) {
cameraRender.texId
} else {
filterRenders[filterRenders.size - 1].texId
}
screenRender.setTexId(texId)
}
fun setFilterAction(filterAction: FilterAction?, position: Int, baseFilterRender: BaseFilterRender) {
when (filterAction) {
FilterAction.SET -> if (filterRenders.size > 0) {
setFilter(position, baseFilterRender)
} else {
addFilter(baseFilterRender)
}
FilterAction.SET_INDEX -> setFilter(position, baseFilterRender)
FilterAction.ADD -> addFilter(baseFilterRender)
FilterAction.ADD_INDEX -> addFilter(position, baseFilterRender)
FilterAction.CLEAR -> clearFilters()
FilterAction.REMOVE -> removeFilter(baseFilterRender)
FilterAction.REMOVE_INDEX -> removeFilter(position)
else -> {}
}
}
fun filtersCount(): Int {
return filterRenders.size
}
fun setPreviewSize(previewWidth: Int, previewHeight: Int) {
for (i in filterRenders.indices) {
filterRenders[i].setPreviewSize(previewWidth, previewHeight)
}
}
fun enableAA(AAEnabled: Boolean) {
screenRender.isAAEnabled = AAEnabled
}
fun isAAEnabled(): Boolean {
return screenRender.isAAEnabled
}
fun updateFrame() {
cameraRender.updateTexImage()
}
fun getSurfaceTexture(): SurfaceTexture {
return cameraRender.surfaceTexture
}
fun getSurface(): Surface {
return cameraRender.surface
}
fun setCameraRotation(rotation: Int) {
cameraRender.setRotation(rotation)
}
fun setCameraFlip(isFlipHorizontal: Boolean, isFlipVertical: Boolean) {
cameraRender.setFlip(isFlipHorizontal, isFlipVertical)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render; package com.pedro.encoder.input.gl.render;
import android.content.Context; import android.content.Context;
@ -5,8 +21,9 @@ import android.graphics.SurfaceTexture;
import android.os.Build; import android.os.Build;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.pedro.encoder.input.gl.FilterAction;
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender; import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -17,13 +34,12 @@ import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ManagerRender { public class ManagerRender {
//Increase it to render more than 1 filter and set filter by position. //Set filter limit. If the number is 0 or less you can add infinity filters
// You must modify it before create your rtmp or rtsp object. public static int numFilters = 0;
public static int numFilters = 1;
private CameraRender cameraRender; private final CameraRender cameraRender;
private List<BaseFilterRender> baseFilterRender = new ArrayList<>(numFilters); private final List<BaseFilterRender> filterRenders;
private ScreenRender screenRender; private final ScreenRender screenRender;
private int width; private int width;
private int height; private int height;
@ -32,8 +48,8 @@ public class ManagerRender {
private Context context; private Context context;
public ManagerRender() { public ManagerRender() {
filterRenders = new ArrayList<>();
cameraRender = new CameraRender(); cameraRender = new CameraRender();
for (int i = 0; i < numFilters; i++) baseFilterRender.add(new NoFilterRender());
screenRender = new ScreenRender(); screenRender = new ScreenRender();
} }
@ -45,33 +61,26 @@ public class ManagerRender {
this.previewWidth = previewWidth; this.previewWidth = previewWidth;
this.previewHeight = previewHeight; this.previewHeight = previewHeight;
cameraRender.initGl(width, height, context, previewWidth, previewHeight); cameraRender.initGl(width, height, context, previewWidth, previewHeight);
for (int i = 0; i < numFilters; i++) {
int textId = i == 0 ? cameraRender.getTexId() : baseFilterRender.get(i - 1).getTexId();
baseFilterRender.get(i).setPreviousTexId(textId);
baseFilterRender.get(i).initGl(width, height, context, previewWidth, previewHeight);
baseFilterRender.get(i).initFBOLink();
}
screenRender.setStreamSize(encoderWidth, encoderHeight); screenRender.setStreamSize(encoderWidth, encoderHeight);
screenRender.setTexId(baseFilterRender.get(numFilters - 1).getTexId()); screenRender.setTexId(cameraRender.getTexId());
screenRender.initGl(context); screenRender.initGl(context);
} }
public void drawOffScreen() { public void drawOffScreen() {
cameraRender.draw(); cameraRender.draw();
for (BaseFilterRender baseFilterRender : baseFilterRender) baseFilterRender.draw(); for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.draw();
} }
public void drawScreen(int width, int height, boolean keepAspectRatio, int mode, int rotation, public void drawScreen(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) { boolean flipStreamVertical, boolean flipStreamHorizontal) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, isPreview); screenRender.draw(width, height, keepAspectRatio, mode, rotation, flipStreamVertical,
flipStreamHorizontal);
} }
public void release() { public void release() {
cameraRender.release(); cameraRender.release();
for (int i = 0; i < this.baseFilterRender.size(); i++) { for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.release();
this.baseFilterRender.get(i).release(); filterRenders.clear();
this.baseFilterRender.set(i, new NoFilterRender());
}
screenRender.release(); screenRender.release();
} }
@ -95,14 +104,99 @@ public class ManagerRender {
return cameraRender.getSurface(); return cameraRender.getSurface();
} }
public void setFilter(int position, BaseFilterRender baseFilterRender) { private void setFilter(int position, BaseFilterRender baseFilterRender) {
final int id = this.baseFilterRender.get(position).getPreviousTexId(); final int id = filterRenders.get(position).getPreviousTexId();
final RenderHandler renderHandler = this.baseFilterRender.get(position).getRenderHandler(); final RenderHandler renderHandler = filterRenders.get(position).getRenderHandler();
this.baseFilterRender.get(position).release(); filterRenders.get(position).release();
this.baseFilterRender.set(position, baseFilterRender); filterRenders.set(position, baseFilterRender);
this.baseFilterRender.get(position).setPreviousTexId(id); filterRenders.get(position).setPreviousTexId(id);
this.baseFilterRender.get(position).initGl(width, height, context, previewWidth, previewHeight); filterRenders.get(position).initGl(width, height, context, previewWidth, previewHeight);
this.baseFilterRender.get(position).setRenderHandler(renderHandler); filterRenders.get(position).setRenderHandler(renderHandler);
}
private void addFilter(BaseFilterRender baseFilterRender) {
filterRenders.add(baseFilterRender);
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight);
baseFilterRender.initFBOLink();
reOrderFilters();
}
private void addFilter(int position, BaseFilterRender baseFilterRender) {
filterRenders.add(position, baseFilterRender);
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight);
baseFilterRender.initFBOLink();
reOrderFilters();
}
private void clearFilters() {
for (BaseFilterRender baseFilterRender: filterRenders) {
baseFilterRender.release();
}
filterRenders.clear();
reOrderFilters();
}
private void removeFilter(int position) {
filterRenders.remove(position).release();
reOrderFilters();
}
private void removeFilter(BaseFilterRender baseFilterRender) {
baseFilterRender.release();
filterRenders.remove(baseFilterRender);
reOrderFilters();
}
private void reOrderFilters() {
for (int i = 0; i < filterRenders.size(); i++) {
int texId = i == 0 ? cameraRender.getTexId() : filterRenders.get(i - 1).getTexId();
filterRenders.get(i).setPreviousTexId(texId);
}
int texId = filterRenders.isEmpty() ? cameraRender.getTexId() :
filterRenders.get(filterRenders.size() - 1).getTexId();
screenRender.setTexId(texId);
}
public void setFilterAction(FilterAction filterAction, int position, BaseFilterRender baseFilterRender) {
switch (filterAction) {
case SET:
if (filterRenders.size() > 0) {
setFilter(position, baseFilterRender);
} else {
addFilter(baseFilterRender);
}
break;
case SET_INDEX:
setFilter(position, baseFilterRender);
break;
case ADD:
if (numFilters > 0 && filterRenders.size() >= numFilters) {
throw new RuntimeException("limit of filters(" + numFilters + ") exceeded");
}
addFilter(baseFilterRender);
break;
case ADD_INDEX:
if (numFilters > 0 && filterRenders.size() >= numFilters) {
throw new RuntimeException("limit of filters(" + numFilters + ") exceeded");
}
addFilter(position, baseFilterRender);
break;
case CLEAR:
clearFilters();
break;
case REMOVE:
removeFilter(baseFilterRender);
break;
case REMOVE_INDEX:
removeFilter(position);
break;
default:
break;
}
}
public int filtersCount() {
return filterRenders.size();
} }
public void setCameraRotation(int rotation) { public void setCameraRotation(int rotation) {
@ -114,8 +208,8 @@ public class ManagerRender {
} }
public void setPreviewSize(int previewWidth, int previewHeight) { public void setPreviewSize(int previewWidth, int previewHeight) {
for (int i = 0; i < this.baseFilterRender.size(); i++) { for (int i = 0; i < filterRenders.size(); i++) {
this.baseFilterRender.get(i).setPreviewSize(previewWidth, previewHeight); filterRenders.get(i).setPreviewSize(previewWidth, previewHeight);
} }
} }
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render; package com.pedro.encoder.input.gl.render;
/** /**

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render; package com.pedro.encoder.input.gl.render;
import android.content.Context; import android.content.Context;
@ -22,11 +38,11 @@ public class ScreenRender {
//rotation matrix //rotation matrix
private final float[] squareVertexData = { private final float[] squareVertexData = {
// X, Y, Z, U, V // X, Y, Z, U, V
-1f, -1f, 0f, 0f, 0f, //bottom left -1f, -1f, 0f, 0f, 0f, //bottom left
1f, -1f, 0f, 1f, 0f, //bottom right 1f, -1f, 0f, 1f, 0f, //bottom right
-1f, 1f, 0f, 0f, 1f, //top left -1f, 1f, 0f, 0f, 1f, //top left
1f, 1f, 0f, 1f, 1f, //top right 1f, 1f, 0f, 1f, 1f, //top right
}; };
private FloatBuffer squareVertex; private FloatBuffer squareVertex;
@ -48,20 +64,18 @@ public class ScreenRender {
private int streamWidth; private int streamWidth;
private int streamHeight; private int streamHeight;
private boolean isPortrait;
public ScreenRender() { public ScreenRender() {
squareVertex = squareVertex =
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES) ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()) .order(ByteOrder.nativeOrder())
.asFloatBuffer(); .asFloatBuffer();
squareVertex.put(squareVertexData).position(0); squareVertex.put(squareVertexData).position(0);
Matrix.setIdentityM(MVPMatrix, 0); Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0); Matrix.setIdentityM(STMatrix, 0);
} }
public void initGl(Context context) { public void initGl(Context context) {
isPortrait = CameraHelper.isPortrait(context);
GlUtil.checkGlError("initGl start"); GlUtil.checkGlError("initGl start");
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex); String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex);
String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.fxaa); String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.fxaa);
@ -78,15 +92,47 @@ public class ScreenRender {
} }
public void draw(int width, int height, boolean keepAspectRatio, int mode, int rotation, public void draw(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) { boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start"); GlUtil.checkGlError("drawScreen start");
if (mode == 2 || mode == 3) { //stream rotation is enabled SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width,height,true,keepAspectRatio, MVPMatrix);
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
}
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth, SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
streamHeight); streamHeight);
draw(width, height);
}
public void drawEncoder(int width, int height, boolean isPortrait, int rotation,
boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start");
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width, height, isPortrait,false, MVPMatrix);
SizeCalculator.calculateViewPortEncoder(width, height, isPortrait);
draw(width, height);
}
public void drawPreview(int width, int height, boolean isPortrait, boolean keepAspectRatio,
int mode, int rotation, boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start");
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, isPortrait,true, MVPMatrix);
float factor = (float) streamWidth / (float) streamHeight;
int w;
int h;
if (factor >= 1f) {
w = isPortrait ? streamHeight : streamWidth;
h = isPortrait ? streamWidth : streamHeight;
} else {
w = isPortrait ? streamWidth : streamHeight;
h = isPortrait ? streamHeight : streamWidth;
}
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, w, h);
draw(width, height);
}
private void draw(int width, int height) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
@ -94,12 +140,12 @@ public class ScreenRender {
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_POS_OFFSET); squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false, GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex); BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aPositionHandle); GLES20.glEnableVertexAttribArray(aPositionHandle);
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_UV_OFFSET); squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false, GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex); BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aTextureHandle); GLES20.glEnableVertexAttribArray(aTextureHandle);
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0); GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render; package com.pedro.encoder.input.gl.render;
import android.content.Context; import android.content.Context;
@ -49,7 +65,6 @@ public class SimpleCameraRender {
private Surface surface; private Surface surface;
private int streamWidth; private int streamWidth;
private int streamHeight; private int streamHeight;
private boolean isPortrait;
public SimpleCameraRender() { public SimpleCameraRender() {
Matrix.setIdentityM(MVPMatrix, 0); Matrix.setIdentityM(MVPMatrix, 0);
@ -86,13 +101,11 @@ public class SimpleCameraRender {
} }
public void drawFrame(int width, int height, boolean keepAspectRatio, int mode, int rotation, public void drawFrame(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) { boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawFrame start"); GlUtil.checkGlError("drawFrame start");
surfaceTexture.getTransformMatrix(STMatrix); surfaceTexture.getTransformMatrix(STMatrix);
if (mode == 2 || mode == 3) { //stream rotation is enabled SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, true, false, MVPMatrix);
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
}
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth, SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
streamHeight); streamHeight);
@ -125,7 +138,6 @@ public class SimpleCameraRender {
* Initializes GL state. Call this after the EGL surface has been created and made current. * Initializes GL state. Call this after the EGL surface has been created and made current.
*/ */
public void initGl(Context context, int streamWidth, int streamHeight) { public void initGl(Context context, int streamWidth, int streamHeight) {
isPortrait = CameraHelper.isPortrait(context);
this.streamWidth = streamWidth; this.streamWidth = streamWidth;
this.streamHeight = streamHeight; this.streamHeight = streamHeight;
GlUtil.checkGlError("initGl start"); GlUtil.checkGlError("initGl start");
@ -139,7 +151,7 @@ public class SimpleCameraRender {
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix"); uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
//camera texture //camera texture
GlUtil.createExternalTextures(1, texturesID, 0); GlUtil.createExternalTextures(texturesID.length, texturesID, 0);
textureID = texturesID[0]; textureID = texturesID[0];
surfaceTexture = new SurfaceTexture(textureID); surfaceTexture = new SurfaceTexture(textureID);
@ -150,8 +162,8 @@ public class SimpleCameraRender {
public void release() { public void release() {
GLES20.glDeleteProgram(program); GLES20.glDeleteProgram(program);
surfaceTexture = null; surfaceTexture.release();
surface = null; surface.release();
} }
public void setFlip(boolean isFlipHorizontal, boolean isFlipVertical) { public void setFlip(boolean isFlipHorizontal, boolean isFlipVertical) {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,8 +1,25 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext; import android.opengl.GLES11Ext;
@ -15,10 +32,13 @@ import android.view.Surface;
import android.view.View; import android.view.View;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.pedro.encoder.R; import com.pedro.encoder.R;
import com.pedro.encoder.input.gl.AndroidViewSprite;
import com.pedro.encoder.utils.gl.GlUtil; import com.pedro.encoder.utils.gl.GlUtil;
import com.pedro.encoder.utils.gl.TranslateTo; import com.pedro.encoder.utils.gl.TranslateTo;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** /**
* Created by pedro on 4/02/18. * Created by pedro on 4/02/18.
@ -44,16 +64,21 @@ public class AndroidViewFilterRender extends BaseFilterRender {
private int uSamplerHandle = -1; private int uSamplerHandle = -1;
private int uSamplerViewHandle = -1; private int uSamplerViewHandle = -1;
private int[] viewId = new int[1]; private int[] viewId = new int[] { -1, -1 };
private View view; private View view;
private SurfaceTexture surfaceTexture; //Use 2 surfaces to avoid block render thread
private Surface surface; private SurfaceTexture surfaceTexture, surfaceTexture2;
private Handler mainHandler; private Surface surface, surface2;
private final Handler mainHandler;
private boolean running = false;
private ExecutorService thread = null;
private boolean hardwareMode = true;
private final AndroidViewSprite sprite;
private volatile Status renderingStatus = Status.DONE1;
private int rotation; private enum Status {
private float positionX, positionY; RENDER1, RENDER2, DONE1, DONE2
private float scaleX = 1f, scaleY = 1f; }
private float viewX, viewY;
public AndroidViewFilterRender() { public AndroidViewFilterRender() {
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES) squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
@ -62,6 +87,7 @@ public class AndroidViewFilterRender extends BaseFilterRender {
squareVertex.put(squareVertexDataFilter).position(0); squareVertex.put(squareVertexDataFilter).position(0);
Matrix.setIdentityM(MVPMatrix, 0); Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0); Matrix.setIdentityM(STMatrix, 0);
sprite = new AndroidViewSprite();
mainHandler = new Handler(Looper.getMainLooper()); mainHandler = new Handler(Looper.getMainLooper());
} }
@ -78,31 +104,37 @@ public class AndroidViewFilterRender extends BaseFilterRender {
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler"); uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView"); uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView");
GlUtil.createExternalTextures(1, viewId, 0); GlUtil.createExternalTextures(viewId.length, viewId, 0);
surfaceTexture = new SurfaceTexture(viewId[0]); surfaceTexture = new SurfaceTexture(viewId[0]);
surfaceTexture2 = new SurfaceTexture(viewId[1]);
surface = new Surface(surfaceTexture); surface = new Surface(surfaceTexture);
surface2 = new Surface(surfaceTexture2);
} }
@Override @Override
protected void drawFilter() { protected void drawFilter() {
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight()); final Status status = renderingStatus;
if (view != null) { switch (status) {
mainHandler.post(new Runnable() { case DONE1:
@Override surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
public void run() { surfaceTexture.updateTexImage();
Canvas canvas = surface.lockCanvas(null); renderingStatus = Status.RENDER2;
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); break;
canvas.translate(positionX, positionY); case DONE2:
canvas.rotate(rotation, viewX / 2f, viewY / 2f); surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
float scaleFactorX = (float) getPreviewWidth() / (float) view.getWidth(); surfaceTexture2.updateTexImage();
float scaleFactorY = (float) getPreviewHeight() / (float) view.getHeight(); renderingStatus = Status.RENDER1;
canvas.scale(scaleX * scaleFactorX, scaleY * scaleFactorY); break;
view.draw(canvas); case RENDER1:
surface.unlockCanvasAndPost(canvas); surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
} surfaceTexture2.updateTexImage();
}); break;
case RENDER2:
default:
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture.updateTexImage();
break;
} }
surfaceTexture.updateTexImage();
GLES20.glUseProgram(program); GLES20.glUseProgram(program);
@ -125,12 +157,27 @@ public class AndroidViewFilterRender extends BaseFilterRender {
//android view //android view
GLES20.glUniform1i(uSamplerViewHandle, 5); GLES20.glUniform1i(uSamplerViewHandle, 5);
GLES20.glActiveTexture(GLES20.GL_TEXTURE5); GLES20.glActiveTexture(GLES20.GL_TEXTURE5);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
switch (status) {
case DONE2:
case RENDER1:
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[1]);
break;
case RENDER2:
case DONE1:
default:
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
break;
}
} }
@Override @Override
public void release() { public void release() {
stopRender();
GLES20.glDeleteProgram(program); GLES20.glDeleteProgram(program);
viewId = new int[] { -1, -1 };
surfaceTexture.release();
surfaceTexture2.release();
} }
public View getView() { public View getView() {
@ -138,11 +185,12 @@ public class AndroidViewFilterRender extends BaseFilterRender {
} }
public void setView(final View view) { public void setView(final View view) {
stopRender();
this.view = view; this.view = view;
if (view != null) { if (view != null) {
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
viewX = view.getMeasuredWidth(); sprite.setView(view);
viewY = view.getMeasuredHeight(); startRender();
} }
} }
@ -152,68 +200,105 @@ public class AndroidViewFilterRender extends BaseFilterRender {
* @param y Position in percent * @param y Position in percent
*/ */
public void setPosition(float x, float y) { public void setPosition(float x, float y) {
int previewX = getPreviewWidth(); sprite.translate(x, y);
int previewY = getPreviewHeight();
this.positionX = previewX * x / 100f;
this.positionY = previewY * y / 100f;
} }
public void setPosition(TranslateTo positionTo) { public void setPosition(TranslateTo positionTo) {
int previewX = getPreviewWidth(); sprite.translate(positionTo);
int previewY = getPreviewHeight();
switch (positionTo) {
case TOP:
this.positionX = previewX / 2f - (viewX / 2f);
this.positionY = 0f;
break;
case LEFT:
this.positionX = 0;
this.positionY = previewY / 2f - (viewY / 2f);
break;
case RIGHT:
this.positionX = previewX - viewX;
this.positionY = previewY / 2f - (viewY / 2f);
break;
case BOTTOM:
this.positionX = previewX / 2f - (viewX / 2f);
this.positionY = previewY - viewY;
break;
case CENTER:
this.positionX = previewX / 2f - (viewX / 2f);
this.positionY = previewY / 2f - (viewY / 2f);
break;
case TOP_RIGHT:
this.positionX = previewX - viewX;
this.positionY = 0;
break;
case BOTTOM_LEFT:
this.positionX = 0;
this.positionY = previewY - viewY;
break;
case BOTTOM_RIGHT:
this.positionX = previewX - viewX;
this.positionY = previewY - viewY;
break;
case TOP_LEFT:
default:
this.positionX = 0;
this.positionY = 0;
break;
}
} }
public void setRotation(int rotation) { public void setRotation(int rotation) {
if (rotation < 0) { sprite.setRotation(rotation);
this.rotation = 0;
} else if (rotation > 360) {
this.rotation = 360;
} else {
this.rotation = rotation;
}
} }
public void setScale(float scaleX, float scaleY) { public void setScale(float scaleX, float scaleY) {
this.scaleX = scaleX; sprite.scale(scaleX, scaleY);
this.scaleY = scaleY;
} }
}
public PointF getScale() {
return sprite.getScale();
}
public PointF getPosition() {
return sprite.getTranslation();
}
public int getRotation() {
return sprite.getRotation();
}
public boolean isHardwareMode() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hardwareMode;
}
/**
* Draw in surface using hardware canvas. True by default
*/
public void setHardwareMode(boolean hardwareMode) {
this.hardwareMode = hardwareMode;
}
private void startRender() {
running = true;
thread = Executors.newSingleThreadExecutor();
thread.execute(() -> {
while (running) {
final Status status = renderingStatus;
if (status == Status.RENDER1 || status == Status.RENDER2) {
final Canvas canvas;
try {
if (isHardwareMode()) {
canvas = status == Status.RENDER1 ? surface.lockHardwareCanvas() : surface2.lockHardwareCanvas();
} else {
canvas = status == Status.RENDER1 ? surface.lockCanvas(null) : surface2.lockCanvas(null);
}
} catch (IllegalStateException e) {
continue;
}
sprite.calculateDefaultScale(getPreviewWidth(), getPreviewHeight());
PointF canvasPosition = sprite.getCanvasPosition(getPreviewWidth(), getPreviewHeight());
PointF canvasScale = sprite.getCanvasScale(getPreviewWidth(), getPreviewHeight());
PointF rotationAxis = sprite.getRotationAxis();
int rotation = sprite.getRotation();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.translate(canvasPosition.x, canvasPosition.y);
canvas.scale(canvasScale.x, canvasScale.y);
canvas.rotate(rotation, rotationAxis.x, rotationAxis.y);
try {
view.draw(canvas);
if (status == Status.RENDER1) {
surface.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE1;
} else {
surface2.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE2;
}
//Sometimes draw could crash if you don't use main thread. Ensuring you can render always
} catch (Exception e) {
mainHandler.post(() -> {
view.draw(canvas);
if (status == Status.RENDER1) {
surface.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE1;
} else {
surface2.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE2;
}
});
}
}
}
});
}
private void stopRender() {
running = false;
if (thread != null) {
thread.shutdownNow();
thread = null;
}
renderingStatus = Status.DONE1;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters;
import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.R;
import com.pedro.encoder.input.gl.Sprite;
import com.pedro.encoder.input.gl.TextureLoader;
import com.pedro.encoder.utils.gl.GlUtil;
import com.pedro.encoder.utils.gl.ImageStreamObject;
import com.pedro.encoder.utils.gl.StreamObjectBase;
import com.pedro.encoder.utils.gl.TranslateTo;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ChromaFilterRender extends BaseFilterRender {
//rotation matrix
private final float[] squareVertexDataFilter = {
// X, Y, Z, U, V
-1f, -1f, 0f, 0f, 0f, //bottom left
1f, -1f, 0f, 1f, 0f, //bottom right
-1f, 1f, 0f, 0f, 1f, //top left
1f, 1f, 0f, 1f, 1f, //top right
};
private int program = -1;
private int aPositionHandle = -1;
private int aTextureHandle = -1;
private int aTextureObjectHandle = -1;
private int uMVPMatrixHandle = -1;
private int uSTMatrixHandle = -1;
private int uSamplerHandle = -1;
private int uObjectHandle = -1;
private int uSensitiveHandle = -1;
private FloatBuffer squareVertexObject;
protected int[] streamObjectTextureId = new int[] { -1 };
protected TextureLoader textureLoader = new TextureLoader();
protected StreamObjectBase streamObject;
private Sprite sprite;
protected boolean shouldLoad = false;
private float sensitive = 0.8f;
public ChromaFilterRender() {
streamObject = new ImageStreamObject();
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertex.put(squareVertexDataFilter).position(0);
sprite = new Sprite();
float[] vertices = sprite.getTransformedVertices();
squareVertexObject = ByteBuffer.allocateDirect(vertices.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertexObject.put(vertices).position(0);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0);
}
@Override
protected void initGlFilter(Context context) {
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.object_vertex);
String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.chroma_fragment);
program = GlUtil.createProgram(vertexShader, fragmentShader);
aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
aTextureHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");
aTextureObjectHandle = GLES20.glGetAttribLocation(program, "aTextureObjectCoord");
uMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
uObjectHandle = GLES20.glGetUniformLocation(program, "uObject");
uSensitiveHandle = GLES20.glGetUniformLocation(program, "uSensitive");
}
@Override
protected void drawFilter() {
if (shouldLoad) {
releaseTexture();
streamObjectTextureId = textureLoader.load(streamObject.getBitmaps());
shouldLoad = false;
}
GLES20.glUseProgram(program);
squareVertex.position(SQUARE_VERTEX_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aPositionHandle);
squareVertex.position(SQUARE_VERTEX_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aTextureHandle);
squareVertexObject.position(SQUARE_VERTEX_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(aTextureObjectHandle, 2, GLES20.GL_FLOAT, false,
2 * FLOAT_SIZE_BYTES, squareVertexObject);
GLES20.glEnableVertexAttribArray(aTextureObjectHandle);
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);
GLES20.glUniformMatrix4fv(uSTMatrixHandle, 1, false, STMatrix, 0);
//Sampler
GLES20.glUniform1i(uSamplerHandle, 4);
GLES20.glActiveTexture(GLES20.GL_TEXTURE4);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, previousTexId);
//Object
GLES20.glUniform1i(uObjectHandle, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, streamObjectTextureId[0]);
GLES20.glUniform1f(uSensitiveHandle, sensitive);
}
@Override
public void release() {
GLES20.glDeleteProgram(program);
releaseTexture();
sprite.reset();
}
private void releaseTexture() {
GLES20.glDeleteTextures(streamObjectTextureId.length, streamObjectTextureId, 0);
streamObjectTextureId = new int[] { -1 };
}
private void setScale(float scaleX, float scaleY) {
sprite.scale(scaleX, scaleY);
squareVertexObject.put(sprite.getTransformedVertices()).position(0);
}
private void setPosition(TranslateTo positionTo) {
sprite.translate(positionTo);
squareVertexObject.put(sprite.getTransformedVertices()).position(0);
}
public void setImage(Bitmap bitmap) {
((ImageStreamObject) streamObject).load(bitmap);
shouldLoad = true;
setScale(100f, 100f);
setPosition(TranslateTo.CENTER);
}
public void setSensitive(float sensitive) {
this.sensitive = sensitive;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;
@ -6,6 +22,7 @@ import android.opengl.Matrix;
import android.os.Build; import android.os.Build;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.pedro.encoder.R; import com.pedro.encoder.R;
import com.pedro.encoder.input.video.CameraHelper;
import com.pedro.encoder.utils.gl.GlUtil; import com.pedro.encoder.utils.gl.GlUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@ -97,9 +114,34 @@ public class RotationFilterRender extends BaseFilterRender {
this.rotation = rotation; this.rotation = rotation;
//Set rotation //Set rotation
Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f); Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f);
Matrix.scaleM(rotationMatrix, 0, 1f, 1f, 0f);
//Translation //Translation
//Matrix.translateM(rotationMatrix, 0, 0f, 0f, 0f); //Matrix.translateM(rotationMatrix, 0, 0f, 0f, 0f);
// Combine the rotation matrix with the projection and camera view // Combine the rotation matrix with the projection and camera view
Matrix.multiplyMM(MVPMatrix, 0, rotationMatrix, 0, MVPMatrix, 0); Matrix.multiplyMM(MVPMatrix, 0, rotationMatrix, 0, MVPMatrix, 0);
} }
/**
* Keep aspect ratio if you rotate 90º or 270º.
* @param rotation value
* @param width width of stream (prepareVideo method) if you are streaming or preview (startPreview method) if you aren't streaming.
* @param height height of stream (prepareVideo method) if you are streaming or preview (startPreview method) if you aren't streaming.
*/
public void setRotationFixed(int rotation, int width, int height, boolean isPortrait) {
this.rotation = rotation;
//Set rotation
Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f);
if (rotation == 90 || rotation == 270) {
float value = (float) height / (float) width;
if (isPortrait) {
Matrix.scaleM(rotationMatrix, 0, value, 1f, 0f);
} else {
Matrix.scaleM(rotationMatrix, 0, 1f, value, 0f);
}
} else {
Matrix.scaleM(rotationMatrix, 0, 1f, 1f, 0f);
}
// Combine the rotation matrix with the projection and camera view
Matrix.multiplyMM(MVPMatrix, 0, rotationMatrix, 0, MVPMatrix, 0);
}
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters; package com.pedro.encoder.input.gl.render.filters;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters.object; package com.pedro.encoder.input.gl.render.filters.object;
import android.content.Context; import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters.object; package com.pedro.encoder.input.gl.render.filters.object;
import android.opengl.GLES20; import android.opengl.GLES20;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters.object; package com.pedro.encoder.input.gl.render.filters.object;
import android.graphics.Bitmap; import android.graphics.Bitmap;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters.object; package com.pedro.encoder.input.gl.render.filters.object;
import android.content.Context; import android.content.Context;
@ -8,6 +24,9 @@ import android.opengl.GLES20;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Build; import android.os.Build;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import com.pedro.encoder.R; import com.pedro.encoder.R;
import com.pedro.encoder.input.gl.Sprite; import com.pedro.encoder.input.gl.Sprite;
@ -25,7 +44,7 @@ import java.nio.FloatBuffer;
public class SurfaceFilterRender extends BaseObjectFilterRender { public class SurfaceFilterRender extends BaseObjectFilterRender {
public interface SurfaceReadyCallback { public interface SurfaceReadyCallback {
void surfaceReady(); void surfaceReady(SurfaceTexture surfaceTexture);
} }
//rotation matrix //rotation matrix
@ -92,11 +111,18 @@ public class SurfaceFilterRender extends BaseObjectFilterRender {
uSamplerSurfaceHandle = GLES20.glGetUniformLocation(program, "uSamplerSurface"); uSamplerSurfaceHandle = GLES20.glGetUniformLocation(program, "uSamplerSurface");
uAlphaHandle = GLES20.glGetUniformLocation(program, "uAlpha"); uAlphaHandle = GLES20.glGetUniformLocation(program, "uAlpha");
GlUtil.createExternalTextures(1, surfaceId, 0); GlUtil.createExternalTextures(surfaceId.length, surfaceId, 0);
surfaceTexture = new SurfaceTexture(surfaceId[0]); surfaceTexture = new SurfaceTexture(surfaceId[0]);
surfaceTexture.setDefaultBufferSize(getWidth(), getHeight()); surfaceTexture.setDefaultBufferSize(getWidth(), getHeight());
surface = new Surface(surfaceTexture); surface = new Surface(surfaceTexture);
if (surfaceReadyCallback != null) surfaceReadyCallback.surfaceReady(); if (surfaceReadyCallback != null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
surfaceReadyCallback.surfaceReady(surfaceTexture);
}
});
}
} }
@Override @Override
@ -142,10 +168,16 @@ public class SurfaceFilterRender extends BaseObjectFilterRender {
surface.release(); surface.release();
} }
/**
* This texture must be renderer using an api called on main thread to avoid possible errors
*/
public SurfaceTexture getSurfaceTexture() { public SurfaceTexture getSurfaceTexture() {
return surfaceTexture; return surfaceTexture;
} }
/**
* This surface must be renderer using an api called on main thread to avoid possible errors
*/
public Surface getSurface() { public Surface getSurface() {
return surface; return surface;
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.gl.render.filters.object; package com.pedro.encoder.input.gl.render.filters.object;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -13,6 +29,11 @@ import com.pedro.encoder.utils.gl.TextStreamObject;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class TextObjectFilterRender extends BaseObjectFilterRender { public class TextObjectFilterRender extends BaseObjectFilterRender {
private String text;
private float textSize;
private int textColor;
private Typeface typeface;
public TextObjectFilterRender() { public TextObjectFilterRender() {
super(); super();
streamObject = new TextStreamObject(); streamObject = new TextStreamObject();
@ -31,7 +52,27 @@ public class TextObjectFilterRender extends BaseObjectFilterRender {
} }
public void setText(String text, float textSize, int textColor, Typeface typeface) { public void setText(String text, float textSize, int textColor, Typeface typeface) {
this.text = text;
this.textSize = textSize;
this.textColor = textColor;
this.typeface = typeface;
((TextStreamObject) streamObject).load(text, textSize, textColor, typeface); ((TextStreamObject) streamObject).load(text, textSize, textColor, typeface);
shouldLoad = true; shouldLoad = true;
} }
public void addText(String text) {
setText(this.text + text, textSize, textColor, typeface);
}
public void updateColor(int textColor) {
setText(this.text + text, textSize, textColor, typeface);
}
public void updateTypeface(Typeface typeface) {
setText(this.text + text, textSize, textColor, typeface);
}
public void updateTextSize(float textSize) {
setText(this.text + text, textSize, textColor, typeface);
}
} }

View File

@ -1,8 +1,25 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.video; package com.pedro.encoder.input.video;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.hardware.Camera; import android.hardware.Camera;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
@ -10,9 +27,11 @@ import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import android.view.View;
import com.pedro.encoder.Frame; import com.pedro.encoder.Frame;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -39,10 +58,11 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
private GetCameraData getCameraData; private GetCameraData getCameraData;
private boolean running = false; private boolean running = false;
private boolean lanternEnable = false; private boolean lanternEnable = false;
private boolean videoStabilizationEnable = false;
private boolean autoFocusEnabled = false;
private int cameraSelect; private int cameraSelect;
private boolean isFrontCamera = false; private CameraHelper.Facing facing = CameraHelper.Facing.BACK;
private boolean isPortrait = false; private boolean isPortrait = false;
private int cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
private Context context; private Context context;
//default parameters for camera //default parameters for camera
@ -55,12 +75,17 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
private List<Camera.Size> previewSizeBack; private List<Camera.Size> previewSizeBack;
private List<Camera.Size> previewSizeFront; private List<Camera.Size> previewSizeFront;
private float distance; private float distance;
private CameraCallbacks cameraCallbacks;
private final int focusAreaSize = 100;
//Face detector //Face detector
public interface FaceDetectorCallback { public interface FaceDetectorCallback {
void onGetFaces(Camera.Face[] faces); void onGetFaces(Camera.Face[] faces, Rect scaleSensor, int sensorOrientation);
} }
private final int sensorOrientation = 0;
//Value obtained from Camera.Face documentation api about bounds
private final Rect faceSensorScale = new Rect(-1000, -1000, 1000, 1000);
private FaceDetectorCallback faceDetectorCallback; private FaceDetectorCallback faceDetectorCallback;
public Camera1ApiManager(SurfaceView surfaceView, GetCameraData getCameraData) { public Camera1ApiManager(SurfaceView surfaceView, GetCameraData getCameraData) {
@ -106,23 +131,36 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
return height; return height;
} }
public void setCameraFacing(CameraHelper.Facing cameraFacing) {
facing = cameraFacing;
}
public void setCameraSelect(int cameraFacing) {
cameraSelect = cameraFacing;
}
public void start(CameraHelper.Facing cameraFacing, int width, int height, int fps) { public void start(CameraHelper.Facing cameraFacing, int width, int height, int fps) {
int facing = cameraFacing == CameraHelper.Facing.BACK ? Camera.CameraInfo.CAMERA_FACING_BACK int facing = cameraFacing == CameraHelper.Facing.BACK ? Camera.CameraInfo.CAMERA_FACING_BACK
: Camera.CameraInfo.CAMERA_FACING_FRONT; : Camera.CameraInfo.CAMERA_FACING_FRONT;
this.width = width; this.width = width;
this.height = height; this.height = height;
this.fps = fps; this.fps = fps;
this.cameraFacing = facing;
cameraSelect = cameraSelect =
facing == Camera.CameraInfo.CAMERA_FACING_BACK ? selectCameraBack() : selectCameraFront(); facing == Camera.CameraInfo.CAMERA_FACING_BACK ? selectCameraBack() : selectCameraFront();
start(); start();
} }
public void start(int facing, int width, int height, int fps) {
this.width = width;
this.height = height;
this.fps = fps;
cameraSelect = facing;
selectCamera(facing);
start();
}
public void start(int width, int height, int fps) { public void start(int width, int height, int fps) {
CameraHelper.Facing facing = start(cameraSelect, width, height, fps);
cameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK ? CameraHelper.Facing.BACK
: CameraHelper.Facing.FRONT;
start(facing, width, height, fps);
} }
private void start() { private void start() {
@ -134,23 +172,27 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
camera = Camera.open(cameraSelect); camera = Camera.open(cameraSelect);
Camera.CameraInfo info = new Camera.CameraInfo(); Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraSelect, info); Camera.getCameraInfo(cameraSelect, info);
isFrontCamera = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT; facing = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK;
isPortrait = context.getResources().getConfiguration().orientation isPortrait = context.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT; == Configuration.ORIENTATION_PORTRAIT;
Camera.Parameters parameters = camera.getParameters(); Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(width, height); parameters.setPreviewSize(width, height);
parameters.setPreviewFormat(imageFormat); parameters.setPreviewFormat(imageFormat);
int[] range = adaptFpsRange(fps, parameters.getSupportedPreviewFpsRange()); int[] range = adaptFpsRange(fps, parameters.getSupportedPreviewFpsRange());
Log.i(TAG, "fps: " + range[0] + " - " + range[1]);
parameters.setPreviewFpsRange(range[0], range[1]); parameters.setPreviewFpsRange(range[0], range[1]);
List<String> supportedFocusModes = parameters.getSupportedFocusModes(); List<String> supportedFocusModes = parameters.getSupportedFocusModes();
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) { if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
autoFocusEnabled = true;
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
autoFocusEnabled = true;
} else { } else {
parameters.setFocusMode(supportedFocusModes.get(0)); parameters.setFocusMode(supportedFocusModes.get(0));
autoFocusEnabled = false;
} }
} }
camera.setParameters(parameters); camera.setParameters(parameters);
@ -168,8 +210,13 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
camera.startPreview(); camera.startPreview();
running = true; running = true;
if (cameraCallbacks != null) {
cameraCallbacks.onCameraOpened();
cameraCallbacks.onCameraChanged(facing);
}
Log.i(TAG, width + "X" + height); Log.i(TAG, width + "X" + height);
} catch (IOException e) { } catch (IOException e) {
if (cameraCallbacks != null) cameraCallbacks.onCameraError(e.getMessage());
Log.e(TAG, "Error", e); Log.e(TAG, "Error", e);
} }
} }
@ -183,6 +230,54 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public void setZoom(int level) {
try {
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
.isZoomSupported()) {
android.hardware.Camera.Parameters params = camera.getParameters();
int maxZoom = params.getMaxZoom();
if (level > maxZoom) level = maxZoom;
else if (level < getMinZoom()) level = getMinZoom();
params.setZoom(level);
camera.setParameters(params);
}
} catch (Exception e) {
Log.e(TAG, "Error", e);
}
}
public int getZoom() {
try {
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
.isZoomSupported()) {
android.hardware.Camera.Parameters params = camera.getParameters();
return params.getZoom();
} else {
return getMinZoom();
}
} catch (Exception e) {
Log.e(TAG, "Error", e);
return getMinZoom();
}
}
public int getMaxZoom() {
try {
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
.isZoomSupported()) {
android.hardware.Camera.Parameters params = camera.getParameters();
return params.getMaxZoom();
} else {
return getMinZoom();
}
} catch (Exception e) {
Log.e(TAG, "Error", e);
return getMinZoom();
}
}
public int getMinZoom() { return 0; }
public void setZoom(MotionEvent event) { public void setZoom(MotionEvent event) {
try { try {
if (camera != null && running && camera.getParameters() != null && camera.getParameters() if (camera != null && running && camera.getParameters() != null && camera.getParameters()
@ -211,6 +306,40 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public void setExposure(int value) {
if (camera != null && camera.getParameters() != null) {
android.hardware.Camera.Parameters params = camera.getParameters();
if (value > params.getMaxExposureCompensation()) value = params.getMaxExposureCompensation();
else if (value < params.getMinExposureCompensation()) value = params.getMinExposureCompensation();
params.setExposureCompensation(value);
camera.setParameters(params);
}
}
public int getExposure() {
if (camera != null && camera.getParameters() != null) {
android.hardware.Camera.Parameters params = camera.getParameters();
return params.getExposureCompensation();
}
return 0;
}
public int getMaxExposure() {
if (camera != null && camera.getParameters() != null) {
android.hardware.Camera.Parameters params = camera.getParameters();
return params.getMaxExposureCompensation();
}
return 0;
}
public int getMinExposure() {
if (camera != null && camera.getParameters() != null) {
android.hardware.Camera.Parameters params = camera.getParameters();
return params.getMinExposureCompensation();
}
return 0;
}
private int selectCameraBack() { private int selectCameraBack() {
return selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK); return selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
} }
@ -250,10 +379,15 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps); int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
for (int[] range : fpsRanges) { for (int[] range : fpsRanges) {
if (range[0] <= expectedFps && range[1] >= expectedFps) { if (range[0] <= expectedFps && range[1] >= expectedFps) {
int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps); int curMeasure = Math.abs(((range[0] + range[1]) / 2) - expectedFps);
if (curMeasure < measure) { if (curMeasure < measure) {
closestRange = range; closestRange = range;
measure = curMeasure; measure = curMeasure;
} else if (curMeasure == measure) {
if (Math.abs(range[0] - expectedFps) < Math.abs(closestRange[1] - expectedFps)) {
closestRange = range;
measure = curMeasure;
}
} }
} }
} }
@ -262,10 +396,22 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
@Override @Override
public void onPreviewFrame(byte[] data, Camera camera) { public void onPreviewFrame(byte[] data, Camera camera) {
getCameraData.inputYUVData(new Frame(data, rotation, isFrontCamera && isPortrait, imageFormat)); getCameraData.inputYUVData(new Frame(data, rotation, facing == CameraHelper.Facing.FRONT && isPortrait, imageFormat));
camera.addCallbackBuffer(yuvBuffer); camera.addCallbackBuffer(yuvBuffer);
} }
public Camera.Size getCameraSize(int width, int height) {
if (camera != null) {
return camera.new Size(width, height);
} else {
camera = Camera.open(cameraSelect);
Camera.Size size = camera.new Size(width, height);
camera.release();
camera = null;
return size;
}
}
/** /**
* See: https://developer.android.com/reference/android/graphics/ImageFormat.html to know name of * See: https://developer.android.com/reference/android/graphics/ImageFormat.html to know name of
* constant values * constant values
@ -335,8 +481,8 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public boolean isFrontCamera() { public CameraHelper.Facing getCameraFacing() {
return isFrontCamera; return facing;
} }
public void switchCamera() throws CameraOpenException { public void switchCamera() throws CameraOpenException {
@ -351,8 +497,6 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
throw new CameraOpenException("This camera resolution cant be opened"); throw new CameraOpenException("This camera resolution cant be opened");
} }
stop(); stop();
cameraFacing = cameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK
? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
start(); start();
return; return;
} }
@ -360,6 +504,20 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public void switchCamera(int cameraId) throws CameraOpenException {
if (camera != null) {
int oldCamera = cameraSelect;
cameraSelect = cameraId;
if (!checkCanOpen()) {
cameraSelect = oldCamera;
throw new CameraOpenException("This camera resolution cant be opened");
}
stop();
start();
return;
}
}
private boolean checkCanOpen() { private boolean checkCanOpen() {
List<Camera.Size> previews; List<Camera.Size> previews;
if (cameraSelect == selectCameraBack()) { if (cameraSelect == selectCameraBack()) {
@ -399,6 +557,23 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public List<int[]> getSupportedFps() {
List<int[]> supportedFps;
if (camera != null) {
supportedFps = camera.getParameters().getSupportedPreviewFpsRange();
} else {
camera = Camera.open(cameraSelect);
supportedFps = camera.getParameters().getSupportedPreviewFpsRange();
camera.release();
camera = null;
}
for (int[] range : supportedFps) {
range[0] /= 1000;
range[1] /= 1000;
}
return supportedFps;
}
/** /**
* @required: <uses-permission android:name="android.permission.FLASHLIGHT"/> * @required: <uses-permission android:name="android.permission.FLASHLIGHT"/>
*/ */
@ -410,6 +585,76 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
lanternEnable = false; lanternEnable = false;
} }
} }
private Camera.AutoFocusCallback autoFocusTakePictureCallback = new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
Log.i(TAG, "tapToFocus success");
} else {
Log.e(TAG, "tapToFocus failed");
}
}
};
public void tapToFocus(View view, MotionEvent event) {
if (camera != null && camera.getParameters() != null) {
Camera.Parameters parameters = camera.getParameters();
if (parameters.getMaxNumMeteringAreas() > 0) {
Rect rect = calculateFocusArea(event.getX(), event.getY(), view.getWidth(), view.getHeight());
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
List<Camera.Area> meteringAreas = new ArrayList<>();
meteringAreas.add(new Camera.Area(rect, 800));
parameters.setFocusAreas(meteringAreas);
try {
camera.setParameters(parameters);
}catch (Exception e) {
Log.i(TAG, "tapToFocus error: " + e.getMessage());
}
}
camera.autoFocus(autoFocusTakePictureCallback);
}
}
public void enableAutoFocus() {
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
autoFocusEnabled = true;
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
autoFocusEnabled = true;
} else {
autoFocusEnabled = false;
parameters.setFocusMode(supportedFocusModes.get(0));
}
}
camera.setParameters(parameters);
}
}
public void disableAutoFocus() {
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
} else {
parameters.setFocusMode(supportedFocusModes.get(0));
}
}
autoFocusEnabled = false;
camera.setParameters(parameters);
}
}
public boolean isAutoFocusEnabled() {
return autoFocusEnabled;
}
public void enableRecordingHint() { public void enableRecordingHint() {
if (camera != null) { if (camera != null) {
@ -427,11 +672,20 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public void enableFaceDetection(FaceDetectorCallback faceDetectorCallback) { public boolean enableFaceDetection(FaceDetectorCallback faceDetectorCallback) {
if (camera != null) { try {
this.faceDetectorCallback = faceDetectorCallback; if (camera != null) {
camera.setFaceDetectionListener(this); this.faceDetectorCallback = faceDetectorCallback;
camera.startFaceDetection(); camera.setFaceDetectionListener(this);
camera.startFaceDetection();
return true;
} else {
Log.e(TAG, "face detection called with camera stopped");
return false;
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "face detection unsupported");
return false;
} }
} }
@ -443,12 +697,61 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
} }
} }
public boolean enableVideoStabilization() {
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
if (parameters.isVideoStabilizationSupported()) {
parameters.setVideoStabilization(true);
videoStabilizationEnable = true;
}
}
return videoStabilizationEnable;
}
public void disableVideoStabilization() {
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
if (parameters.isVideoStabilizationSupported()) {
parameters.setVideoStabilization(false);
videoStabilizationEnable = false;
}
}
}
public boolean isVideoStabilizationEnabled() {
return videoStabilizationEnable;
}
public void setCameraCallbacks(CameraCallbacks cameraCallbacks) {
this.cameraCallbacks = cameraCallbacks;
}
public boolean isFaceDetectionEnabled() { public boolean isFaceDetectionEnabled() {
return faceDetectorCallback != null; return faceDetectorCallback != null;
} }
@Override @Override
public void onFaceDetection(Camera.Face[] faces, Camera camera) { public void onFaceDetection(Camera.Face[] faces, Camera camera) {
if (faceDetectorCallback != null) faceDetectorCallback.onGetFaces(faces); if (faceDetectorCallback != null) faceDetectorCallback.onGetFaces(faces, faceSensorScale, sensorOrientation);
}
private Rect calculateFocusArea(float x, float y, float previewWidth, float previewHeight) {
int left = clamp((int) (x / previewWidth * 2000f - 1000f), focusAreaSize);
int top = clamp((int) (y / previewHeight * 2000f - 1000f), focusAreaSize);
return new Rect(left, top, left + focusAreaSize, top + focusAreaSize);
}
private int clamp(int touchCoordinateInCameraReper, int focusAreaSize) {
int result;
if (Math.abs(touchCoordinateInCameraReper) + focusAreaSize / 2 > 1000){
if (touchCoordinateInCameraReper > 0){
result = 1000 - focusAreaSize / 2;
} else {
result = -1000 + focusAreaSize / 2;
}
} else{
result = touchCoordinateInCameraReper - focusAreaSize / 2;
}
return result;
} }
} }

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.video;
public interface CameraCallbacks {
void onCameraChanged(CameraHelper.Facing facing);
void onCameraError(String error);
void onCameraOpened();
void onCameraDisconnected();
}

View File

@ -1,10 +1,30 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.video; package com.pedro.encoder.input.video;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.util.Range;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface; import android.view.Surface;
import android.view.WindowManager; import android.view.WindowManager;
import androidx.annotation.RequiresApi;
/** /**
* Created by pedro on 17/12/18. * Created by pedro on 17/12/18.
*/ */
@ -54,6 +74,20 @@ public class CameraHelper {
return (float) Math.sqrt(x * x + y * y); return (float) Math.sqrt(x * x + y * y);
} }
/**
* Method to fix camera2 quality related with fps range.
* Add a device if needed.
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static boolean discardCamera2Fps(Range<Integer> range, Facing facing) {
//On Google pixel 4a, 30 and 15 fps ranges produce quality problems with camera2 using facing back.
//Build.MODEL.equalsIgnoreCase("Pixel 4") maybe necessary in future.
if (Build.MODEL.equalsIgnoreCase("Pixel 4a")) {
return facing == Facing.BACK && (range.getUpper() == 30 || range.getUpper() == 15);
}
return false;
}
public enum Facing { public enum Facing {
BACK, FRONT BACK, FRONT
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.video; package com.pedro.encoder.input.video;
/** /**

View File

@ -1,82 +0,0 @@
package com.pedro.encoder.input.video;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.hardware.Camera;
import android.hardware.camera2.params.Face;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.view.View;
/**
* Created by pedro on 17/10/18.
*/
public class FaceDetectorUtil {
public class FaceParsed {
private PointF position;
private PointF scale;
public FaceParsed(PointF position, PointF scale) {
this.position = position;
this.scale = scale;
}
public PointF getPosition() {
return position;
}
public void setPosition(PointF position) {
this.position = position;
}
public PointF getScale() {
return scale;
}
public void setScale(PointF scale) {
this.scale = scale;
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public FaceParsed camera2Parse(Face face, View view, PointF scale, int rotation,
boolean isFrontCamera) {
//Parse face
RectF rect = new RectF(face.getBounds());
Matrix matrix = new Matrix();
matrix.setScale(isFrontCamera ? -1 : 1, 1);
matrix.postRotate(rotation);
matrix.postScale(1f, 1f);
matrix.postTranslate(view.getWidth(), view.getHeight());
matrix.mapRect(rect);
return getFace(rect, scale, view);
}
public FaceParsed camera1Parse(Camera.Face face, View view, PointF scale, int rotation,
boolean isFrontCamera) {
//Parse face
RectF rect = new RectF(face.rect);
Matrix matrix = new Matrix();
matrix.setScale(isFrontCamera ? -1 : 1, 1);
matrix.postRotate(rotation);
matrix.postScale(view.getWidth() / 2000f, view.getHeight() / 2000f);
matrix.postTranslate(view.getWidth() / 2f, view.getHeight() / 2f);
matrix.mapRect(rect);
return getFace(rect, scale, view);
}
private FaceParsed getFace(RectF rectF, PointF scale, View view) {
//Position
float posX = rectF.centerX() * 100 / view.getWidth();
float posY = rectF.centerY() * 100 / view.getHeight();
PointF positionParsed = new PointF(posX - scale.x / 2, posY - scale.y / 2);
//Scale
float scaleX = rectF.width() * 100 / view.getWidth();
float scaleY = rectF.height() * 100 / view.getHeight();
PointF scaleParsed = new PointF(scaleX, scaleY);
return new FaceParsed(positionParsed, scaleParsed);
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.video; package com.pedro.encoder.input.video;
/** /**

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.input.video; package com.pedro.encoder.input.video;
import com.pedro.encoder.Frame; import com.pedro.encoder.Frame;

View File

@ -1,8 +1,27 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.utils; package com.pedro.encoder.utils;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaCodecList; import android.media.MediaCodecList;
import android.os.Build; import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -18,6 +37,8 @@ public class CodecUtil {
public static final String H264_MIME = "video/avc"; public static final String H264_MIME = "video/avc";
public static final String H265_MIME = "video/hevc"; public static final String H265_MIME = "video/hevc";
public static final String AAC_MIME = "audio/mp4a-latm"; public static final String AAC_MIME = "audio/mp4a-latm";
public static final String VORBIS_MIME = "audio/ogg";
public static final String OPUS_MIME = "audio/opus";
public enum Force { public enum Force {
FIRST_COMPATIBLE_FOUND, SOFTWARE, HARDWARE FIRST_COMPATIBLE_FOUND, SOFTWARE, HARDWARE
@ -27,110 +48,134 @@ public class CodecUtil {
List<MediaCodecInfo> mediaCodecInfoList = getAllCodecs(false); List<MediaCodecInfo> mediaCodecInfoList = getAllCodecs(false);
List<String> infos = new ArrayList<>(); List<String> infos = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
String info = "----------------\n"; StringBuilder info = new StringBuilder("----------------\n");
info += "Name: " + mediaCodecInfo.getName() + "\n"; info.append("Name: ")
.append(mediaCodecInfo.getName())
.append("\n");
for (String type : mediaCodecInfo.getSupportedTypes()) { for (String type : mediaCodecInfo.getSupportedTypes()) {
info += "Type: " + type + "\n"; info.append("Type: ")
.append(type)
.append("\n");
MediaCodecInfo.CodecCapabilities codecCapabilities = MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(type); mediaCodecInfo.getCapabilitiesForType(type);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
info += "Max instances: " + codecCapabilities.getMaxSupportedInstances() + "\n"; info.append("Max instances: ")
.append(codecCapabilities.getMaxSupportedInstances())
.append("\n");
} }
if (mediaCodecInfo.isEncoder()) { if (mediaCodecInfo.isEncoder()) {
info += "----- Encoder info -----\n"; info.append("----- Encoder info -----\n");
MediaCodecInfo.EncoderCapabilities encoderCapabilities = null; MediaCodecInfo.EncoderCapabilities encoderCapabilities = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
encoderCapabilities = codecCapabilities.getEncoderCapabilities(); encoderCapabilities = codecCapabilities.getEncoderCapabilities();
info += "Complexity range: " info.append("Complexity range: ")
+ encoderCapabilities.getComplexityRange().getLower() .append(encoderCapabilities.getComplexityRange().getLower())
+ " - " .append(" - ")
+ encoderCapabilities.getComplexityRange().getUpper() .append(encoderCapabilities.getComplexityRange().getUpper())
+ "\n"; .append("\n");
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
info += "Quality range: " info.append("Quality range: ")
+ encoderCapabilities.getQualityRange().getLower() .append(encoderCapabilities.getQualityRange().getLower())
+ " - " .append(" - ")
+ encoderCapabilities.getQualityRange().getUpper() .append(encoderCapabilities.getQualityRange().getUpper())
+ "\n"; .append("\n");
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
info += "CBR supported: " + encoderCapabilities.isBitrateModeSupported( info.append("CBR supported: ")
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) + "\n"; .append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR))
info += "VBR supported: " + encoderCapabilities.isBitrateModeSupported( .append("\n")
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) + "\n"; .append("VBR supported: ")
info += "CQ supported: " + encoderCapabilities.isBitrateModeSupported( .append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR))
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ) + "\n"; .append("\n")
.append("CQ supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ))
.append("\n");
} }
info += "----- -----\n"; info.append("----- -----\n");
} else { } else {
info += "----- Decoder info -----\n"; info.append("----- Decoder info -----\n")
info += "----- -----\n"; .append("----- -----\n");
} }
if (codecCapabilities.colorFormats != null && codecCapabilities.colorFormats.length > 0) { if (codecCapabilities.colorFormats != null && codecCapabilities.colorFormats.length > 0) {
info += "----- Video info -----\n"; info.append("----- Video info -----\n")
info += "Supported colors: \n"; .append("Supported colors: \n");
for (int color : codecCapabilities.colorFormats) info += color + "\n"; for (int color : codecCapabilities.colorFormats)
info.append(color)
.append("\n");
for (MediaCodecInfo.CodecProfileLevel profile : codecCapabilities.profileLevels) for (MediaCodecInfo.CodecProfileLevel profile : codecCapabilities.profileLevels)
info += "Profile: " + profile.profile + ", level: " + profile.level + "\n"; info.append("Profile: ")
.append(profile.profile)
.append(", level: ")
.append(profile.level)
.append("\n");
MediaCodecInfo.VideoCapabilities videoCapabilities = null; MediaCodecInfo.VideoCapabilities videoCapabilities = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoCapabilities = codecCapabilities.getVideoCapabilities(); videoCapabilities = codecCapabilities.getVideoCapabilities();
info += "Bitrate range: " info.append("Bitrate range: ")
+ videoCapabilities.getBitrateRange().getLower() .append(videoCapabilities.getBitrateRange().getLower())
+ " - " .append(" - ")
+ videoCapabilities.getBitrateRange().getUpper() .append(videoCapabilities.getBitrateRange().getUpper())
+ "\n"; .append("\n")
info += "Frame rate range: " .append("Frame rate range: ")
+ videoCapabilities.getSupportedFrameRates().getLower() .append(videoCapabilities.getSupportedFrameRates().getLower())
+ " - " .append(" - ")
+ videoCapabilities.getSupportedFrameRates().getUpper() .append(videoCapabilities.getSupportedFrameRates().getUpper())
+ "\n"; .append("\n")
info += "Width range: " .append("Width range: ")
+ videoCapabilities.getSupportedWidths().getLower() .append(videoCapabilities.getSupportedWidths().getLower())
+ " - " .append(" - ")
+ videoCapabilities.getSupportedWidths().getUpper() .append(videoCapabilities.getSupportedWidths().getUpper())
+ "\n"; .append("\n")
info += "Height range: " .append("Height range: ")
+ videoCapabilities.getSupportedHeights().getLower() .append(videoCapabilities.getSupportedHeights().getLower())
+ " - " .append(" - ")
+ videoCapabilities.getSupportedHeights().getUpper() .append(videoCapabilities.getSupportedHeights().getUpper())
+ "\n"; .append("\n");
} }
info += "----- -----\n"; info.append("----- -----\n");
} else { } else {
info += "----- Audio info -----\n"; info.append("----- Audio info -----\n");
for (MediaCodecInfo.CodecProfileLevel profile : codecCapabilities.profileLevels) for (MediaCodecInfo.CodecProfileLevel profile : codecCapabilities.profileLevels)
info += "Profile: " + profile.profile + ", level: " + profile.level + "\n"; info.append("Profile: ")
.append(profile.profile)
.append(", level: ")
.append(profile.level)
.append("\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecInfo.AudioCapabilities audioCapabilities = MediaCodecInfo.AudioCapabilities audioCapabilities =
codecCapabilities.getAudioCapabilities(); codecCapabilities.getAudioCapabilities();
info += "Bitrate range: " info.append("Bitrate range: ")
+ audioCapabilities.getBitrateRange().getLower() .append(audioCapabilities.getBitrateRange().getLower())
+ " - " .append(" - ")
+ audioCapabilities.getBitrateRange().getUpper() .append(audioCapabilities.getBitrateRange().getUpper())
+ "\n"; .append("\n")
info += "Channels supported: " + audioCapabilities.getMaxInputChannelCount() + "\n"; .append("Channels supported: ")
.append(audioCapabilities.getMaxInputChannelCount())
.append("\n");
try { try {
if (audioCapabilities.getSupportedSampleRates() != null if (audioCapabilities.getSupportedSampleRates() != null
&& audioCapabilities.getSupportedSampleRates().length > 0) { && audioCapabilities.getSupportedSampleRates().length > 0) {
info += "Supported sample rate: \n"; info.append("Supported sample rate: \n");
for (int sr : audioCapabilities.getSupportedSampleRates()) info += sr + "\n"; for (int sr : audioCapabilities.getSupportedSampleRates())
info.append(sr)
.append("\n");
} }
} catch (Exception e) { } catch (Exception ignored) { }
}
} }
info += "----- -----\n"; info.append("----- -----\n");
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
info += "Max instances: " + codecCapabilities.getMaxSupportedInstances() + "\n"; info.append("Max instances: ")
.append(codecCapabilities.getMaxSupportedInstances())
.append("\n");
} }
} }
info += "----------------\n"; info.append("----------------\n");
infos.add(info); infos.add(info.toString());
} }
return infos; return infos;
} }
@ -151,18 +196,30 @@ public class CodecUtil {
return filterBroken ? filterBrokenCodecs(mediaCodecInfoList) : mediaCodecInfoList; return filterBroken ? filterBrokenCodecs(mediaCodecInfoList) : mediaCodecInfoList;
} }
public static List<MediaCodecInfo> getAllHardwareEncoders(String mime) { public static List<MediaCodecInfo> getAllHardwareEncoders(String mime, boolean cbrPriority) {
List<MediaCodecInfo> mediaCodecInfoList = getAllEncoders(mime); List<MediaCodecInfo> mediaCodecInfoList = getAllEncoders(mime);
List<MediaCodecInfo> mediaCodecInfoHardware = new ArrayList<>(); List<MediaCodecInfo> mediaCodecInfoHardware = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfoHardwareCBR = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isHardwareAccelerated(mediaCodecInfo)) { if (isHardwareAccelerated(mediaCodecInfo)) {
mediaCodecInfoHardware.add(mediaCodecInfo); mediaCodecInfoHardware.add(mediaCodecInfo);
if (cbrPriority &&Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& isCBRModeSupported(mediaCodecInfo, mime)) {
mediaCodecInfoHardwareCBR.add(mediaCodecInfo);
}
} }
} }
mediaCodecInfoHardware.removeAll(mediaCodecInfoHardwareCBR);
mediaCodecInfoHardware.addAll(0, mediaCodecInfoHardwareCBR);
return mediaCodecInfoHardware; return mediaCodecInfoHardware;
} }
public static List<MediaCodecInfo> getAllHardwareDecoders(String mime) { public static List<MediaCodecInfo> getAllHardwareEncoders(String mime) {
return getAllHardwareEncoders(mime, false);
}
public static List<MediaCodecInfo> getAllHardwareDecoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = getAllDecoders(mime); List<MediaCodecInfo> mediaCodecInfoList = getAllDecoders(mime);
List<MediaCodecInfo> mediaCodecInfoHardware = new ArrayList<>(); List<MediaCodecInfo> mediaCodecInfoHardware = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
@ -173,18 +230,30 @@ public class CodecUtil {
return mediaCodecInfoHardware; return mediaCodecInfoHardware;
} }
public static List<MediaCodecInfo> getAllSoftwareEncoders(String mime) { public static List<MediaCodecInfo> getAllSoftwareEncoders(String mime, boolean cbrPriority) {
List<MediaCodecInfo> mediaCodecInfoList = getAllEncoders(mime); List<MediaCodecInfo> mediaCodecInfoList = getAllEncoders(mime);
List<MediaCodecInfo> mediaCodecInfoSoftware = new ArrayList<>(); List<MediaCodecInfo> mediaCodecInfoSoftware = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfoSoftwareCBR = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isSoftwareOnly(mediaCodecInfo)) { if (isSoftwareOnly(mediaCodecInfo)) {
mediaCodecInfoSoftware.add(mediaCodecInfo); mediaCodecInfoSoftware.add(mediaCodecInfo);
if (cbrPriority &&Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& isCBRModeSupported(mediaCodecInfo, mime)) {
mediaCodecInfoSoftwareCBR.add(mediaCodecInfo);
}
} }
} }
mediaCodecInfoSoftware.removeAll(mediaCodecInfoSoftwareCBR);
mediaCodecInfoSoftware.addAll(0, mediaCodecInfoSoftwareCBR);
return mediaCodecInfoSoftware; return mediaCodecInfoSoftware;
} }
public static List<MediaCodecInfo> getAllSoftwareDecoders(String mime) { public static List<MediaCodecInfo> getAllSoftwareEncoders(String mime) {
return getAllSoftwareEncoders(mime, false);
}
public static List<MediaCodecInfo> getAllSoftwareDecoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = getAllDecoders(mime); List<MediaCodecInfo> mediaCodecInfoList = getAllDecoders(mime);
List<MediaCodecInfo> mediaCodecInfoSoftware = new ArrayList<>(); List<MediaCodecInfo> mediaCodecInfoSoftware = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
@ -196,7 +265,7 @@ public class CodecUtil {
} }
/** /**
* choose the video encoder by mime. * choose encoder by mime.
*/ */
public static List<MediaCodecInfo> getAllEncoders(String mime) { public static List<MediaCodecInfo> getAllEncoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>(); List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
@ -215,9 +284,26 @@ public class CodecUtil {
return mediaCodecInfoList; return mediaCodecInfoList;
} }
/** public static List<MediaCodecInfo> getAllEncoders(String mime, boolean hardwarePriority, boolean cbrPriority) {
* choose the video encoder by mime. List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
*/ if (hardwarePriority) {
mediaCodecInfoList.addAll(getAllHardwareEncoders(mime, cbrPriority));
mediaCodecInfoList.addAll(getAllSoftwareEncoders(mime, cbrPriority));
} else {
mediaCodecInfoList.addAll(getAllEncoders(mime));
}
return mediaCodecInfoList;
}
public static List<MediaCodecInfo> getAllEncoders(String mime, boolean hardwarePriority) {
return getAllEncoders(mime, hardwarePriority, false);
}
/**
* choose decoder by mime.
*/
public static List<MediaCodecInfo> getAllDecoders(String mime) { public static List<MediaCodecInfo> getAllDecoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>(); List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfos = getAllCodecs(true); List<MediaCodecInfo> mediaCodecInfos = getAllCodecs(true);
@ -235,6 +321,17 @@ public class CodecUtil {
return mediaCodecInfoList; return mediaCodecInfoList;
} }
public static List<MediaCodecInfo> getAllDecoders(String mime, boolean hardwarePriority) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
if (hardwarePriority) {
mediaCodecInfoList.addAll(getAllHardwareDecoders(mime));
mediaCodecInfoList.addAll(getAllSoftwareDecoders(mime));
} else {
mediaCodecInfoList.addAll(getAllDecoders(mime));
}
return mediaCodecInfoList;
}
/* Adapted from google/ExoPlayer /* Adapted from google/ExoPlayer
* https://github.com/google/ExoPlayer/commit/48555550d7fcf6953f2382466818c74092b26355 * https://github.com/google/ExoPlayer/commit/48555550d7fcf6953f2382466818c74092b26355
*/ */
@ -252,7 +349,9 @@ public class CodecUtil {
*/ */
private static boolean isSoftwareOnly(MediaCodecInfo mediaCodecInfo) { private static boolean isSoftwareOnly(MediaCodecInfo mediaCodecInfo) {
if (Build.VERSION.SDK_INT >= 29) { if (Build.VERSION.SDK_INT >= 29) {
return mediaCodecInfo.isSoftwareOnly(); //mediaCodecInfo.isSoftwareOnly() is not working on emulators.
//Use !mediaCodecInfo.isHardwareAccelerated() to make sure that all codecs are classified as software or hardware
return !mediaCodecInfo.isHardwareAccelerated();
} }
String name = mediaCodecInfo.getName().toLowerCase(); String name = mediaCodecInfo.getName().toLowerCase();
if (name.startsWith("arc.")) { // App Runtime for Chrome (ARC) codecs if (name.startsWith("arc.")) { // App Runtime for Chrome (ARC) codecs
@ -267,6 +366,16 @@ public class CodecUtil {
|| (!name.startsWith("omx.") && !name.startsWith("c2.")); || (!name.startsWith("omx.") && !name.startsWith("c2."));
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static boolean isCBRModeSupported(MediaCodecInfo mediaCodecInfo, String mime) {
MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(mime);
MediaCodecInfo.EncoderCapabilities encoderCapabilities =
codecCapabilities.getEncoderCapabilities();
return encoderCapabilities.isBitrateModeSupported(
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
}
/** /**
* Filter broken codecs by name and device model. * Filter broken codecs by name and device model.
* *
@ -279,11 +388,27 @@ public class CodecUtil {
*/ */
private static List<MediaCodecInfo> filterBrokenCodecs(List<MediaCodecInfo> codecs) { private static List<MediaCodecInfo> filterBrokenCodecs(List<MediaCodecInfo> codecs) {
List<MediaCodecInfo> listFilter = new ArrayList<>(); List<MediaCodecInfo> listFilter = new ArrayList<>();
List<MediaCodecInfo> listLowPriority = new ArrayList<>();
List<MediaCodecInfo> listUltraLowPriority = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : codecs) { for (MediaCodecInfo mediaCodecInfo : codecs) {
if (isValid(mediaCodecInfo.getName())) { if (isValid(mediaCodecInfo.getName())) {
listFilter.add(mediaCodecInfo); CodecPriority priority = checkCodecPriority(mediaCodecInfo.getName());
switch (priority) {
case ULTRA_LOW:
listUltraLowPriority.add(mediaCodecInfo);
break;
case LOW:
listLowPriority.add(mediaCodecInfo);
break;
case NORMAL:
default:
listFilter.add(mediaCodecInfo);
break;
}
} }
} }
listFilter.addAll(listLowPriority);
listFilter.addAll(listUltraLowPriority);
return listFilter; return listFilter;
} }
@ -291,6 +416,26 @@ public class CodecUtil {
* For now, none broken codec reported. * For now, none broken codec reported.
*/ */
private static boolean isValid(String name) { private static boolean isValid(String name) {
//This encoder is invalid and produce errors (Only found in AVD API 16)
if (name.equalsIgnoreCase("aacencoder")) return false;
return true; return true;
} }
private enum CodecPriority {
NORMAL, LOW, ULTRA_LOW
}
/**
* Few devices have codecs that is not working properly in few cases like using AWS MediaLive or YouTube
* but it is still usable in most of cases.
* @return priority level.
*/
private static CodecPriority checkCodecPriority(String name) {
//maybe only broke on samsung with Android 12+ using YouTube and AWS MediaLive
// but set as ultra low priority in all cases.
if (name.equalsIgnoreCase("c2.sec.aac.encoder")) return CodecPriority.ULTRA_LOW;
//broke on few devices using YouTube and AWS MediaLive
else if (name.equalsIgnoreCase("omx.google.aac.encoder")) return CodecPriority.LOW;
else return CodecPriority.NORMAL;
}
} }

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.utils;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Camera;
import android.hardware.camera2.params.Face;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.input.video.CameraHelper;
/**
* Created by pedro on 17/10/18.
*/
public class FaceDetectorUtil {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static FaceParsed camera2Parse(Face face, Rect scaleSensor, int sensorOrientation, int rotation, CameraHelper.Facing facing) {
return cameraParse(face.getBounds(), scaleSensor, sensorOrientation, rotation, facing, 100);
}
public static FaceParsed camera1Parse(Camera.Face face, Rect scaleSensor, int sensorOrientation, int rotation, CameraHelper.Facing facing) {
return cameraParse(face.rect, scaleSensor, sensorOrientation, rotation, facing, 50);
}
/**
* Parse bounds from camera1 and camera2 api to scale and position used in OpenGlView filters.
*/
private static FaceParsed cameraParse(Rect face, Rect scaleSensor, int sensorOrientation, int streamRotation,
CameraHelper.Facing facing, float translate) {
// Face Detection Matrix
Matrix matrix = new Matrix();
// Need mirror for front camera.
matrix.setScale(facing == CameraHelper.Facing.FRONT ? -1 : 1, 1);
matrix.postRotate(streamRotation);
// 100f because we are doing scale value by percent to work with filters.
float s1 = 100f / scaleSensor.width();
float s2 = 100f / scaleSensor.height();
// Camera2 sensor could be rotated. We need check it and fix.
if (sensorOrientation == 90 || sensorOrientation == 270) {
matrix.postScale(s2, s1);
} else {
matrix.postScale(s1, s2);
}
matrix.postTranslate(translate, translate);
Rect uRect = new Rect(face);
RectF rectF = new RectF(uRect);
matrix.mapRect(rectF);
uRect = new Rect((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom);
// Filter is draw from top left corner. Move position from center to corner.
PointF positionParsed = new PointF(uRect.centerX() - uRect.width() / 2f, uRect.centerY() - uRect.height() / 2f);
PointF sizeParsed = new PointF(uRect.width(), uRect.height());
return new FaceParsed(positionParsed, sizeParsed);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* 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.pedro.encoder.utils;
import android.graphics.PointF;
/**
* Created by pedro on 18/04/21.
*/
public class FaceParsed {
private PointF position;
private PointF scale;
public FaceParsed(PointF position, PointF scale) {
this.position = position;
this.scale = scale;
}
public PointF getPosition() {
return position;
}
public void setPosition(PointF position) {
this.position = position;
}
public PointF getScale() {
return scale;
}
public void setScale(PointF scale) {
this.scale = scale;
}
@Override
public String toString() {
return "FaceParsed{" +
"position=" + position +
", scale=" + scale +
'}';
}
}

Some files were not shown because too many files have changed in this diff Show More