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" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

View File

@ -26,5 +26,10 @@
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</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>
</project>

View File

@ -5,6 +5,13 @@
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</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">
<output url="file://$PROJECT_DIR$/build/classes" />
</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'
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
compileSdkVersion 32
buildToolsVersion "32.0.0"
defaultConfig {
applicationId "fr.mobdev.peertubelive"
minSdkVersion 21
targetSdkVersion 31
targetSdkVersion 32
versionCode 2
versionName "1.1"
@ -39,6 +39,7 @@ android {
buildFeatures {
dataBinding true
}
namespace 'fr.mobdev.peertubelive'
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.mobdev.peertubelive">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<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.databinding.DataBindingUtil
import com.pedro.encoder.input.video.CameraHelper
import com.pedro.rtmp.utils.ConnectCheckerRtmp
import com.pedro.rtplibrary.rtmp.RtmpCamera2
import net.ossrs.rtmp.ConnectCheckerRtmp
import fr.mobdev.peertubelive.R
import fr.mobdev.peertubelive.databinding.StreamBinding
import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA
import fr.mobdev.peertubelive.objects.StreamData
import java.util.*
import kotlin.collections.ArrayList
class StreamActivity : AppCompatActivity() {
@ -50,10 +52,11 @@ class StreamActivity : AppCompatActivity() {
private var surfaceInit: Boolean = false
private var permissionGiven: Boolean = false
private var streamIsActive: Boolean = false
private var screenOrientation: Int = 0
private var screenOrientation: Int = -1
private var lastScreenOrientation: Int = 0
private var orientationCounter: Int = 0
private var rotationIsLanternEnabled: Boolean = true
private var rotationIsEnabled: Boolean = true
private var orientationTimer: Timer = Timer()
companion object {
const val BACKGROUND :Int = 1
@ -69,46 +72,11 @@ class StreamActivity : AppCompatActivity() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding = DataBindingUtil.setContentView(this, R.layout.stream)
orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){
override fun onOrientationChanged(orientation: Int) {
if(orientation < 0 || !rotationIsLanternEnabled)
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()
}
handlerOrientation(orientation)
}
}
orientationEventListener.enable()
@ -144,14 +112,11 @@ class StreamActivity : AppCompatActivity() {
binding.flash.visibility = View.GONE
binding.rotation.setOnClickListener {
if (rotationIsLanternEnabled) {
rotationIsLanternEnabled = !rotationIsLanternEnabled
if (rotationIsEnabled)
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
}
else {
rotationIsLanternEnabled = !rotationIsLanternEnabled
else
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
}
rotationIsEnabled = !rotationIsEnabled
}
binding.rotation.visibility = View.GONE
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() {
streamIsActive = true
binding.permissionInfo.visibility = View.GONE
@ -271,6 +282,10 @@ class StreamActivity : AppCompatActivity() {
binding.flash.visibility = View.VISIBLE
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
override fun onConnectionStartedRtmp(rtmpUrl: String) {
}
override fun onConnectionSuccessRtmp() {
runOnUiThread {
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
@ -347,7 +362,8 @@ class StreamActivity : AppCompatActivity() {
//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)
} 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) */

View File

@ -41,7 +41,7 @@
android:layout_height="0dp"
android:id="@+id/surfaceView"
app:keepAspectRatio="true"
app:aspectRatioMode="adjust_rotate"
app:aspectRatioMode="adjust"
app:AAEnabled="false"
app:numFilters="1"
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.
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 {
google()
jcenter()
mavenCentral()
}
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 '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
// in the individual module build.gradle files
@ -18,7 +20,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}

View File

@ -1,11 +1,13 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
android {
compileSdkVersion 30
compileSdkVersion 32
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
targetSdkVersion 32
}
buildTypes {
release {
@ -13,8 +15,11 @@ android {
consumerProguardFiles 'proguard-rules.pro'
}
}
namespace 'com.pedro.encoder'
}
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;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.utils.CodecUtil;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* Created by pedro on 18/09/19.
*/
public abstract class BaseEncoder implements EncoderCallback {
private static final String TAG = "BaseEncoder";
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
protected String TAG = "BaseEncoder";
private final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
private HandlerThread handlerThread;
protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<>(80);
protected MediaCodec codec;
protected long presentTimeUs;
protected static long presentTimeUs;
protected volatile boolean running = false;
protected boolean isBufferMode = true;
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() {
if (presentTimeUs == 0) {
presentTimeUs = System.nanoTime() / 1000;
}
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);
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() {
stop(true);
}
public void stop(boolean resetTs) {
if (resetTs) {
presentTimeUs = 0;
}
running = false;
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 {
codec.stop();
codec.release();
@ -39,18 +147,19 @@ public abstract class BaseEncoder implements EncoderCallback {
} catch (IllegalStateException | NullPointerException e) {
codec = null;
}
oldTimeStamp = 0L;
}
protected abstract MediaCodecInfo chooseEncoder(String mime);
protected void getDataFromEncoder(Frame frame) throws IllegalStateException {
protected void getDataFromEncoder() throws IllegalStateException {
if (isBufferMode) {
int inBufferIndex = codec.dequeueInputBuffer(0);
if (inBufferIndex >= 0) {
inputAvailable(codec, inBufferIndex, frame);
inputAvailable(codec, inBufferIndex);
}
}
for (; running; ) {
while (running) {
int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat mediaFormat = codec.getOutputFormat();
@ -65,16 +174,22 @@ public abstract class BaseEncoder implements EncoderCallback {
protected abstract Frame getInputFrame() throws InterruptedException;
protected abstract long calculatePts(Frame frame, long presentTimeUs);
private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
int inBufferIndex, Frame frame) throws IllegalStateException {
int inBufferIndex) throws IllegalStateException {
try {
if (frame == null) frame = getInputFrame();
Frame frame = getInputFrame();
while (frame == null) frame = getInputFrame();
byteBuffer.clear();
byteBuffer.put(frame.getBuffer(), frame.getOffset(), frame.getSize());
long pts = System.nanoTime() / 1000 - presentTimeUs;
mediaCodec.queueInputBuffer(inBufferIndex, 0, frame.getSize(), pts, 0);
int size = Math.max(0, Math.min(frame.getSize(), byteBuffer.remaining()) - frame.getOffset());
byteBuffer.put(frame.getBuffer(), frame.getOffset(), size);
long pts = calculatePts(frame, presentTimeUs);
mediaCodec.queueInputBuffer(inBufferIndex, 0, size, pts, 0);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (NullPointerException | IndexOutOfBoundsException e) {
Log.i(TAG, "Encoding error", e);
}
}
@ -100,7 +215,7 @@ public abstract class BaseEncoder implements EncoderCallback {
}
@Override
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
throws IllegalStateException {
ByteBuffer byteBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ -108,7 +223,7 @@ public abstract class BaseEncoder implements EncoderCallback {
} else {
byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
}
processInput(byteBuffer, mediaCodec, inBufferIndex, frame);
processInput(byteBuffer, mediaCodec, inBufferIndex);
}
@Override
@ -122,4 +237,41 @@ public abstract class BaseEncoder implements EncoderCallback {
}
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;
import android.media.MediaCodec;
@ -8,7 +24,7 @@ import androidx.annotation.NonNull;
* Created by pedro on 18/09/19.
*/
public interface EncoderCallback {
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
throws IllegalStateException;
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;
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;
import android.media.MediaCodec;
@ -7,11 +23,10 @@ import android.util.Log;
import androidx.annotation.NonNull;
import com.pedro.encoder.BaseEncoder;
import com.pedro.encoder.Frame;
import com.pedro.encoder.GetFrame;
import com.pedro.encoder.input.audio.GetMicrophoneData;
import com.pedro.encoder.utils.CodecUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
@ -22,15 +37,18 @@ import java.util.List;
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
private static final String TAG = "AudioEncoder";
private GetAacData getAacData;
private final GetAacData getAacData;
private int bitRate = 64 * 1024; //in kbps
private int sampleRate = 32000; //in hz
private int maxInputSize = 0;
private boolean isStereo = true;
private GetFrame getFrame;
private long bytesRead = 0;
private boolean tsModeBuffer = false;
public AudioEncoder(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,
int maxInputSize) {
this.bitRate = bitRate;
this.sampleRate = sampleRate;
this.maxInputSize = maxInputSize;
this.isStereo = isStereo;
isBufferMode = true;
try {
List<MediaCodecInfo> encoders = new ArrayList<>();
if (force == CodecUtil.Force.HARDWARE) {
encoders = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
} else if (force == CodecUtil.Force.SOFTWARE) {
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;
}
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
if (encoder != null) {
Log.i(TAG, "Encoder selected " + encoder.getName());
codec = MediaCodec.createByCodecName(encoder.getName());
} else {
if (encoders.isEmpty()) {
Log.e(TAG, "Valid encoder not found");
return false;
} else {
codec = MediaCodec.createByCodecName(encoders.get(0).getName());
}
Log.e(TAG, "Valid encoder not found");
return false;
}
int channelCount = (isStereo) ? 2 : 1;
@ -76,41 +82,65 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
running = false;
Log.i(TAG, "prepared");
return true;
} catch (IOException | IllegalStateException e) {
} catch (Exception e) {
Log.e(TAG, "Create AudioEncoder failed.", e);
this.stop();
return false;
}
}
public void setGetFrame(GetFrame getFrame) {
this.getFrame = getFrame;
}
/**
* Prepare encoder with default parameters
*/
public boolean prepareAudioEncoder() {
return prepareAudioEncoder(bitRate, sampleRate, isStereo, 0);
return prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
}
@Override
public void start(boolean resetTs) {
presentTimeUs = System.nanoTime() / 1000;
codec.start();
running = true;
shouldReset = resetTs;
Log.i(TAG, "started");
}
@Override
protected void stopImp() {
bytesRead = 0;
Log.i(TAG, "stopped");
}
@Override
public void reset() {
stop(false);
prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
restart();
}
@Override
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
protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
@NonNull MediaCodec.BufferInfo bufferInfo) {
fixTimeStamp(bufferInfo);
}
@Override
@ -123,39 +153,45 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
* Set custom PCM data.
* Use it after prepareAudioEncoder(int sampleRate, int channel).
* Used too with microphone.
*
*/
@Override
public void inputPCMData(Frame frame) {
if (running) {
try {
getDataFromEncoder(frame);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
}
} else {
if (running && !queue.offer(frame)) {
Log.i(TAG, "frame discarded");
}
}
@Override
protected MediaCodecInfo chooseEncoder(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = CodecUtil.getAllEncoders(mime);
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
String name = mediaCodecInfo.getName().toLowerCase();
if (!name.contains("omx.google")) return mediaCodecInfo;
}
if (mediaCodecInfoList.size() > 0) {
return mediaCodecInfoList.get(0);
List<MediaCodecInfo> mediaCodecInfoList;
if (force == CodecUtil.Force.HARDWARE) {
mediaCodecInfoList = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
} else if (force == CodecUtil.Force.SOFTWARE) {
mediaCodecInfoList = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
} 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) {
this.sampleRate = sampleRate;
}
public boolean isTsModeBuffer() {
return tsModeBuffer;
}
public void setTsModeBuffer(boolean tsModeBuffer) {
if (!isRunning()) {
this.tsModeBuffer = tsModeBuffer;
}
}
@Override
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat 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;
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;
import android.media.audiofx.AcousticEchoCanceler;
@ -13,7 +29,7 @@ public class AudioPostProcessEffect {
private final String TAG = "AudioPostProcessEffect";
private int microphoneId;
private final int microphoneId;
private AcousticEchoCanceler acousticEchoCanceler = null;
private AutomaticGainControl automaticGainControl = null;
private NoiseSuppressor noiseSuppressor = null;
@ -25,11 +41,13 @@ public class AudioPostProcessEffect {
public void enableAutoGainControl() {
if (AutomaticGainControl.isAvailable() && automaticGainControl == null) {
automaticGainControl = AutomaticGainControl.create(microphoneId);
automaticGainControl.setEnabled(true);
Log.i(TAG, "AutoGainControl enabled");
} else {
Log.e(TAG, "This device don't support AutoGainControl");
if (automaticGainControl != null) {
automaticGainControl.setEnabled(true);
Log.i(TAG, "AutoGainControl enabled");
return;
}
}
Log.e(TAG, "This device doesn't implement AutoGainControl");
}
public void releaseAutoGainControl() {
@ -43,11 +61,13 @@ public class AudioPostProcessEffect {
public void enableEchoCanceler() {
if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) {
acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId);
acousticEchoCanceler.setEnabled(true);
Log.i(TAG, "EchoCanceler enabled");
} else {
Log.e(TAG, "This device don't support EchoCanceler");
if (acousticEchoCanceler != null) {
acousticEchoCanceler.setEnabled(true);
Log.i(TAG, "EchoCanceler enabled");
return;
}
}
Log.e(TAG, "This device doesn't implement EchoCanceler");
}
public void releaseEchoCanceler() {
@ -61,11 +81,13 @@ public class AudioPostProcessEffect {
public void enableNoiseSuppressor() {
if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) {
noiseSuppressor = NoiseSuppressor.create(microphoneId);
noiseSuppressor.setEnabled(true);
Log.i(TAG, "NoiseSuppressor enabled");
} else {
Log.e(TAG, "This device don't support NoiseSuppressor");
if (noiseSuppressor != null) {
noiseSuppressor.setEnabled(true);
Log.i(TAG, "NoiseSuppressor enabled");
return;
}
}
Log.e(TAG, "This device doesn't implement NoiseSuppressor");
}
public void releaseNoiseSuppressor() {
@ -75,4 +97,10 @@ public class AudioPostProcessEffect {
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;
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;
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;
import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.AudioPlaybackCaptureConfiguration;
import android.media.AudioRecord;
@ -16,28 +33,29 @@ import java.nio.ByteBuffer;
* Created by pedro on 19/01/17.
*/
@SuppressLint("MissingPermission")
public class MicrophoneManager {
private final String TAG = "MicrophoneManager";
private static final int BUFFER_SIZE = 4096;
private int BUFFER_SIZE = 0;
protected AudioRecord audioRecord;
private GetMicrophoneData getMicrophoneData;
private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
private byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
private final GetMicrophoneData getMicrophoneData;
protected byte[] pcmBuffer = new byte[BUFFER_SIZE];
protected byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
protected boolean running = false;
private boolean created = false;
//default parameters for microphone
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 boolean muted = false;
protected boolean muted = false;
private AudioPostProcessEffect audioPostProcessEffect;
HandlerThread handlerThread;
private CustomAudioEffect customAudioEffect = new NoAudioEffect();
protected HandlerThread handlerThread;
protected CustomAudioEffect customAudioEffect = new NoAudioEffect();
public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
this.getMicrophoneData = getMicrophoneData;
getPcmBufferSize();
}
public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) {
@ -55,61 +73,86 @@ public class MicrophoneManager {
/**
* 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) {
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
* @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,
boolean noiseSuppressor) {
this.sampleRate = sampleRate;
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO;
audioRecord =
new AudioRecord(audioSource, sampleRate, channel, audioFormat,
getPcmBufferSize());
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
created = true;
public boolean createMicrophone(int audioSource, int sampleRate, boolean isStereo,
boolean echoCanceler, boolean noiseSuppressor) {
try {
this.sampleRate = sampleRate;
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, getMaxInputSize() * 5);
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalArgumentException("Some parameters specified is not valid");
}
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
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling this!
*
* @param config - AudioPlaybackCaptureConfiguration received from {@link android.media.projection.MediaProjection}
* Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal
* audio
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling
* this!
*
* @param config - AudioPlaybackCaptureConfiguration received from {@link
* android.media.projection.MediaProjection}
* @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection)
* @see "https://developer.android.com/guide/topics/media/playback-capture"
* @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9"
*/
public void createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate, boolean isStereo) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.sampleRate = sampleRate;
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord.Builder()
.setAudioPlaybackCaptureConfig(config)
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channel)
.build())
.setBufferSizeInBytes(getPcmBufferSize())
.build();
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
String chl = (isStereo) ? "Stereo" : "Mono";
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
created = true;
} else createMicrophone(sampleRate, isStereo, false, false);
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.sampleRate = sampleRate;
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord.Builder().setAudioPlaybackCaptureConfig(config)
.setAudioFormat(new AudioFormat.Builder().setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channel)
.build())
.setBufferSizeInBytes(getMaxInputSize() * 5)
.build();
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
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
@ -126,8 +169,6 @@ public class MicrophoneManager {
Frame frame = read();
if (frame != null) {
getMicrophoneData.inputPCMData(frame);
} else {
running = false;
}
}
}
@ -160,14 +201,10 @@ public class MicrophoneManager {
/**
* @return Object with size and PCM buffer data
*/
private Frame read() {
pcmBuffer.rewind();
int size = audioRecord.read(pcmBuffer, pcmBuffer.remaining());
if (size <= 0) {
return null;
}
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer.array()),
muted ? 0 : pcmBuffer.arrayOffset(), size);
protected Frame read() {
int size = audioRecord.read(pcmBuffer, 0, pcmBuffer.length);
if (size < 0) return null;
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer), 0, size);
}
/**
@ -190,8 +227,7 @@ public class MicrophoneManager {
audioRecord = null;
}
if (audioPostProcessEffect != null) {
audioPostProcessEffect.releaseEchoCanceler();
audioPostProcessEffect.releaseNoiseSuppressor();
audioPostProcessEffect.release();
}
Log.i(TAG, "Microphone stopped");
}
@ -199,10 +235,10 @@ public class MicrophoneManager {
/**
* Get PCM buffer size
*/
private int getPcmBufferSize() {
int pcmBufSize =
AudioRecord.getMinBufferSize(sampleRate, channel, AudioFormat.ENCODING_PCM_16BIT);
return pcmBufSize * 5;
private void getPcmBufferSize() {
BUFFER_SIZE = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
pcmBuffer = new byte[BUFFER_SIZE];
pcmBufferMuted = new byte[BUFFER_SIZE];
}
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;
import android.os.HandlerThread;
import android.util.Log;
import com.pedro.encoder.Frame;
import com.pedro.encoder.GetFrame;
import java.nio.ByteBuffer;
/**
* Similar to MicrophoneManager but samples are not read automatically.
* 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";
@ -54,4 +72,13 @@ public class MicrophoneManagerManual extends MicrophoneManager {
handlerThread = new HandlerThread("nothing");
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;
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;
import android.media.MediaCodec;
@ -6,160 +22,110 @@ import android.media.MediaFormat;
import android.util.Log;
import com.pedro.encoder.Frame;
import com.pedro.encoder.input.audio.GetMicrophoneData;
import com.pedro.encoder.utils.CodecUtil;
import com.pedro.encoder.utils.PCMUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Created by pedro on 20/06/17.
*/
public class AudioDecoder {
private final String TAG = "AudioDecoder";
public class AudioDecoder extends BaseDecoder {
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 MediaFormat audioFormat;
private String mime = "";
private int sampleRate;
private boolean isStereo;
private int channels = 1;
private int size = 2048;
private byte[] pcmBuffer = new byte[size];
private byte[] pcmBufferMuted = new byte[11];
private static boolean loopMode = false;
private boolean muted = false;
private long duration;
private volatile long seekTime = 0;
private volatile long startMs = 0;
public AudioDecoder(GetMicrophoneData getMicrophoneData,
AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) {
super(loopFileInterface);
TAG = "AudioDecoder";
this.getMicrophoneData = getMicrophoneData;
this.audioDecoderInterface = audioDecoderInterface;
this.loopFileInterface = loopFileInterface;
}
public boolean initExtractor(String filePath) throws IOException {
decoding = false;
audioExtractor = new MediaExtractor();
audioExtractor.setDataSource(filePath);
@Override
protected boolean extract(MediaExtractor audioExtractor) {
size = 2048;
running = false;
for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) {
audioFormat = audioExtractor.getTrackFormat(i);
mime = audioFormat.getString(MediaFormat.KEY_MIME);
mediaFormat = audioExtractor.getTrackFormat(i);
mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio/")) {
audioExtractor.selectTrack(i);
} else {
audioFormat = null;
mediaFormat = null;
}
}
if (audioFormat != null) {
channels = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
if (mediaFormat != null) {
channels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
isStereo = channels >= 2;
sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
duration = audioFormat.getLong(MediaFormat.KEY_DURATION);
if (channels >= 2) {
pcmBuffer = new byte[2048 * channels];
}
sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
fixBuffer();
return true;
//audio decoder not supported
} else {
mime = "";
audioFormat = null;
mediaFormat = null;
return false;
}
}
private void fixBuffer() {
if (channels >= 2) {
size *= channels;
}
pcmBuffer = new byte[size];
}
public boolean prepareAudio() {
try {
audioDecoder = MediaCodec.createDecoderByType(mime);
audioDecoder.configure(audioFormat, null, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
return prepare(null);
}
public void start() {
decoding = true;
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 reset() {
resetCodec(null);
}
public void stop() {
decoding = false;
seekTime = 0;
if (thread != null) {
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();
@Override
protected void decode() {
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
startMs = System.currentTimeMillis();
while (decoding) {
int inIndex = audioDecoder.dequeueInputBuffer(10000);
while (running) {
int inIndex = codec.dequeueInputBuffer(10000);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = audioExtractor.readSampleData(buffer, 0);
int sampleSize = extractor.readSampleData(buffer, 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 {
audioDecoder.queueInputBuffer(inIndex, 0, sampleSize, audioExtractor.getSampleTime(), 0);
audioExtractor.advance();
codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
int outIndex = audioDecoder.dequeueOutputBuffer(audioInfo, 10000);
int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = audioDecoder.getOutputBuffers();
outputBuffers = codec.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
case MediaCodec.INFO_TRY_AGAIN_LATER:
break;
default:
//needed for fix decode speed
while (audioExtractor.getSampleTime() / 1000
> System.currentTimeMillis() - startMs + seekTime) {
long extractorTs = extractor.getSampleTime() / 1000;
long currentTs = System.currentTimeMillis() - startMs + seekTime;
if (extractorTs > currentTs) {
try {
Thread.sleep(10);
long sleepTime = extractorTs - currentTs;
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
if (thread != null) thread.interrupt();
Thread.currentThread().interrupt();
return;
}
}
@ -167,13 +133,13 @@ public class AudioDecoder {
//This buffer is PCM data
if (muted) {
outBuffer.get(pcmBufferMuted, 0,
outBuffer.remaining() <= pcmBufferMuted.length ? outBuffer.remaining()
: pcmBufferMuted.length);
Math.min(outBuffer.remaining(), pcmBufferMuted.length));
getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length));
} else {
outBuffer.get(pcmBuffer, 0,
outBuffer.remaining() <= pcmBuffer.length ? outBuffer.remaining()
: pcmBuffer.length);
if (pcmBuffer.length < outBuffer.remaining()) {
pcmBuffer = new byte[outBuffer.remaining()];
}
outBuffer.get(pcmBuffer, 0, Math.min(outBuffer.remaining(), pcmBuffer.length));
if (channels > 2) { //downgrade to stereo
byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels);
getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length));
@ -181,42 +147,54 @@ public class AudioDecoder {
getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length));
}
}
audioDecoder.releaseOutputBuffer(outIndex, false);
codec.releaseOutputBuffer(outIndex, false);
break;
}
}
// All decoded frames have been rendered, we can stop playing now
if ((audioInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0;
Log.i(TAG, "end of file out");
if (loopMode) {
loopFileInterface.onReset(false);
} else {
audioDecoderInterface.onAudioDecoderFinished();
}
// All decoded frames have been rendered, we can stop playing now
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0;
Log.i(TAG, "end of file out");
running = false;
if (loopMode) {
loopFileInterface.onReset(false);
} else {
audioDecoderInterface.onAudioDecoderFinished();
}
}
}
}
public double getTime() {
if (decoding) {
return audioExtractor.getSampleTime() / 10E5;
/**
* This method should be called after prepare.
* 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 {
Log.i(TAG, "default input size");
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() {
muted = true;
}
@ -236,8 +214,4 @@ public class AudioDecoder {
public boolean 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;
/**

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;
/**

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

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

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;
import android.opengl.EGL14;
@ -19,45 +35,29 @@ import com.pedro.encoder.utils.gl.GlUtil;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SurfaceManager {
private static final String TAG = "SurfaceManager";
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private EGLContext eglContext = null;
private EGLSurface eglSurface = null;
private EGLDisplay eglDisplay = null;
private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
private volatile boolean isReady = false;
/**
* Creates an EGL context and an EGL surface.
*/
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 boolean isReady() {
return isReady;
}
public void makeCurrent() {
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
Log.e("Error", "eglMakeCurrent failed");
Log.e(TAG, "eglMakeCurrent failed");
}
}
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.
*/
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);
if (eglDisplay == EGL14.EGL_NO_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.
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[] {
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
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, EGL_RECORDABLE_ANDROID, 1,
/* 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 {
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, EGL_RECORDABLE_ANDROID, 1,
/* 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
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,
/* 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
};
}
EGLConfig[] configs = new EGLConfig[1];
@ -119,7 +143,7 @@ public class SurfaceManager {
// Create a window surface, and attach it to the Surface we received.
if (surface == null) {
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);
} else {
@ -129,6 +153,28 @@ public class SurfaceManager {
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0);
}
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.eglReleaseThread();
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() {

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;
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;
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;
import android.content.Context;
@ -63,7 +79,7 @@ public class CameraRender extends BaseRenderOffScreen {
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
//camera texture
GlUtil.createExternalTextures(1, textureID, 0);
GlUtil.createExternalTextures(textureID.length, textureID, 0);
surfaceTexture = new SurfaceTexture(textureID[0]);
surfaceTexture.setDefaultBufferSize(width, height);
surface = new Surface(surfaceTexture);
@ -107,8 +123,8 @@ public class CameraRender extends BaseRenderOffScreen {
@Override
public void release() {
GLES20.glDeleteProgram(program);
surfaceTexture = null;
surface = null;
surfaceTexture.release();
surface.release();
}
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;
import android.content.Context;
@ -5,8 +21,9 @@ 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;
import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
import java.util.ArrayList;
import java.util.List;
@ -17,13 +34,12 @@ import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ManagerRender {
//Increase it to render more than 1 filter and set filter by position.
// You must modify it before create your rtmp or rtsp object.
public static int numFilters = 1;
//Set filter limit. If the number is 0 or less you can add infinity filters
public static int numFilters = 0;
private CameraRender cameraRender;
private List<BaseFilterRender> baseFilterRender = new ArrayList<>(numFilters);
private ScreenRender screenRender;
private final CameraRender cameraRender;
private final List<BaseFilterRender> filterRenders;
private final ScreenRender screenRender;
private int width;
private int height;
@ -32,8 +48,8 @@ public class ManagerRender {
private Context context;
public ManagerRender() {
filterRenders = new ArrayList<>();
cameraRender = new CameraRender();
for (int i = 0; i < numFilters; i++) baseFilterRender.add(new NoFilterRender());
screenRender = new ScreenRender();
}
@ -45,33 +61,26 @@ public class ManagerRender {
this.previewWidth = previewWidth;
this.previewHeight = 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.setTexId(baseFilterRender.get(numFilters - 1).getTexId());
screenRender.setTexId(cameraRender.getTexId());
screenRender.initGl(context);
}
public void drawOffScreen() {
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,
boolean isPreview) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, isPreview);
boolean flipStreamVertical, boolean flipStreamHorizontal) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, flipStreamVertical,
flipStreamHorizontal);
}
public void release() {
cameraRender.release();
for (int i = 0; i < this.baseFilterRender.size(); i++) {
this.baseFilterRender.get(i).release();
this.baseFilterRender.set(i, new NoFilterRender());
}
for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.release();
filterRenders.clear();
screenRender.release();
}
@ -95,14 +104,99 @@ public class ManagerRender {
return cameraRender.getSurface();
}
public void setFilter(int position, BaseFilterRender baseFilterRender) {
final int id = this.baseFilterRender.get(position).getPreviousTexId();
final RenderHandler renderHandler = this.baseFilterRender.get(position).getRenderHandler();
this.baseFilterRender.get(position).release();
this.baseFilterRender.set(position, baseFilterRender);
this.baseFilterRender.get(position).setPreviousTexId(id);
this.baseFilterRender.get(position).initGl(width, height, context, previewWidth, previewHeight);
this.baseFilterRender.get(position).setRenderHandler(renderHandler);
private void setFilter(int position, BaseFilterRender baseFilterRender) {
final int id = filterRenders.get(position).getPreviousTexId();
final RenderHandler renderHandler = filterRenders.get(position).getRenderHandler();
filterRenders.get(position).release();
filterRenders.set(position, baseFilterRender);
filterRenders.get(position).setPreviousTexId(id);
filterRenders.get(position).initGl(width, height, context, previewWidth, previewHeight);
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) {
@ -114,8 +208,8 @@ public class ManagerRender {
}
public void setPreviewSize(int previewWidth, int previewHeight) {
for (int i = 0; i < this.baseFilterRender.size(); i++) {
this.baseFilterRender.get(i).setPreviewSize(previewWidth, previewHeight);
for (int i = 0; i < filterRenders.size(); i++) {
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;
/**

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;
import android.content.Context;
@ -22,11 +38,11 @@ public class ScreenRender {
//rotation matrix
private final float[] squareVertexData = {
// 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
// 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 FloatBuffer squareVertex;
@ -48,20 +64,18 @@ public class ScreenRender {
private int streamWidth;
private int streamHeight;
private boolean isPortrait;
public ScreenRender() {
squareVertex =
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertex.put(squareVertexData).position(0);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0);
}
public void initGl(Context context) {
isPortrait = CameraHelper.isPortrait(context);
GlUtil.checkGlError("initGl start");
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex);
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,
boolean isPreview) {
boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start");
if (mode == 2 || mode == 3) { //stream rotation is enabled
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
}
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width,height,true,keepAspectRatio, MVPMatrix);
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.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);
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);
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_UV_OFFSET);
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.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;
import android.content.Context;
@ -49,7 +65,6 @@ public class SimpleCameraRender {
private Surface surface;
private int streamWidth;
private int streamHeight;
private boolean isPortrait;
public SimpleCameraRender() {
Matrix.setIdentityM(MVPMatrix, 0);
@ -86,13 +101,11 @@ public class SimpleCameraRender {
}
public void drawFrame(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) {
boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawFrame start");
surfaceTexture.getTransformMatrix(STMatrix);
if (mode == 2 || mode == 3) { //stream rotation is enabled
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
}
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, true, false, MVPMatrix);
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
streamHeight);
@ -125,7 +138,6 @@ public class SimpleCameraRender {
* Initializes GL state. Call this after the EGL surface has been created and made current.
*/
public void initGl(Context context, int streamWidth, int streamHeight) {
isPortrait = CameraHelper.isPortrait(context);
this.streamWidth = streamWidth;
this.streamHeight = streamHeight;
GlUtil.checkGlError("initGl start");
@ -139,7 +151,7 @@ public class SimpleCameraRender {
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
//camera texture
GlUtil.createExternalTextures(1, texturesID, 0);
GlUtil.createExternalTextures(texturesID.length, texturesID, 0);
textureID = texturesID[0];
surfaceTexture = new SurfaceTexture(textureID);
@ -150,8 +162,8 @@ public class SimpleCameraRender {
public void release() {
GLES20.glDeleteProgram(program);
surfaceTexture = null;
surface = null;
surfaceTexture.release();
surface.release();
}
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;
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;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
@ -15,10 +32,13 @@ import android.view.Surface;
import android.view.View;
import androidx.annotation.RequiresApi;
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.TranslateTo;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by pedro on 4/02/18.
@ -44,16 +64,21 @@ public class AndroidViewFilterRender extends BaseFilterRender {
private int uSamplerHandle = -1;
private int uSamplerViewHandle = -1;
private int[] viewId = new int[1];
private int[] viewId = new int[] { -1, -1 };
private View view;
private SurfaceTexture surfaceTexture;
private Surface surface;
private Handler mainHandler;
//Use 2 surfaces to avoid block render thread
private SurfaceTexture surfaceTexture, surfaceTexture2;
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 float positionX, positionY;
private float scaleX = 1f, scaleY = 1f;
private float viewX, viewY;
private enum Status {
RENDER1, RENDER2, DONE1, DONE2
}
public AndroidViewFilterRender() {
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
@ -62,6 +87,7 @@ public class AndroidViewFilterRender extends BaseFilterRender {
squareVertex.put(squareVertexDataFilter).position(0);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0);
sprite = new AndroidViewSprite();
mainHandler = new Handler(Looper.getMainLooper());
}
@ -78,31 +104,37 @@ public class AndroidViewFilterRender extends BaseFilterRender {
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView");
GlUtil.createExternalTextures(1, viewId, 0);
GlUtil.createExternalTextures(viewId.length, viewId, 0);
surfaceTexture = new SurfaceTexture(viewId[0]);
surfaceTexture2 = new SurfaceTexture(viewId[1]);
surface = new Surface(surfaceTexture);
surface2 = new Surface(surfaceTexture2);
}
@Override
protected void drawFilter() {
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
if (view != null) {
mainHandler.post(new Runnable() {
@Override
public void run() {
Canvas canvas = surface.lockCanvas(null);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.translate(positionX, positionY);
canvas.rotate(rotation, viewX / 2f, viewY / 2f);
float scaleFactorX = (float) getPreviewWidth() / (float) view.getWidth();
float scaleFactorY = (float) getPreviewHeight() / (float) view.getHeight();
canvas.scale(scaleX * scaleFactorX, scaleY * scaleFactorY);
view.draw(canvas);
surface.unlockCanvasAndPost(canvas);
}
});
final Status status = renderingStatus;
switch (status) {
case DONE1:
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture.updateTexImage();
renderingStatus = Status.RENDER2;
break;
case DONE2:
surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture2.updateTexImage();
renderingStatus = Status.RENDER1;
break;
case RENDER1:
surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture2.updateTexImage();
break;
case RENDER2:
default:
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture.updateTexImage();
break;
}
surfaceTexture.updateTexImage();
GLES20.glUseProgram(program);
@ -125,12 +157,27 @@ public class AndroidViewFilterRender extends BaseFilterRender {
//android view
GLES20.glUniform1i(uSamplerViewHandle, 5);
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
public void release() {
stopRender();
GLES20.glDeleteProgram(program);
viewId = new int[] { -1, -1 };
surfaceTexture.release();
surfaceTexture2.release();
}
public View getView() {
@ -138,11 +185,12 @@ public class AndroidViewFilterRender extends BaseFilterRender {
}
public void setView(final View view) {
stopRender();
this.view = view;
if (view != null) {
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
viewX = view.getMeasuredWidth();
viewY = view.getMeasuredHeight();
sprite.setView(view);
startRender();
}
}
@ -152,68 +200,105 @@ public class AndroidViewFilterRender extends BaseFilterRender {
* @param y Position in percent
*/
public void setPosition(float x, float y) {
int previewX = getPreviewWidth();
int previewY = getPreviewHeight();
this.positionX = previewX * x / 100f;
this.positionY = previewY * y / 100f;
sprite.translate(x, y);
}
public void setPosition(TranslateTo positionTo) {
int previewX = getPreviewWidth();
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;
}
sprite.translate(positionTo);
}
public void setRotation(int rotation) {
if (rotation < 0) {
this.rotation = 0;
} else if (rotation > 360) {
this.rotation = 360;
} else {
this.rotation = rotation;
}
sprite.setRotation(rotation);
}
public void setScale(float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
sprite.scale(scaleX, 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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
import android.content.Context;
@ -6,6 +22,7 @@ import android.opengl.Matrix;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.R;
import com.pedro.encoder.input.video.CameraHelper;
import com.pedro.encoder.utils.gl.GlUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -97,9 +114,34 @@ public class RotationFilterRender extends BaseFilterRender {
this.rotation = rotation;
//Set rotation
Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f);
Matrix.scaleM(rotationMatrix, 0, 1f, 1f, 0f);
//Translation
//Matrix.translateM(rotationMatrix, 0, 0f, 0f, 0f);
// Combine the rotation matrix with the projection and camera view
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
import android.content.Context;
@ -8,6 +24,9 @@ import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import com.pedro.encoder.R;
import com.pedro.encoder.input.gl.Sprite;
@ -25,7 +44,7 @@ import java.nio.FloatBuffer;
public class SurfaceFilterRender extends BaseObjectFilterRender {
public interface SurfaceReadyCallback {
void surfaceReady();
void surfaceReady(SurfaceTexture surfaceTexture);
}
//rotation matrix
@ -92,11 +111,18 @@ public class SurfaceFilterRender extends BaseObjectFilterRender {
uSamplerSurfaceHandle = GLES20.glGetUniformLocation(program, "uSamplerSurface");
uAlphaHandle = GLES20.glGetUniformLocation(program, "uAlpha");
GlUtil.createExternalTextures(1, surfaceId, 0);
GlUtil.createExternalTextures(surfaceId.length, surfaceId, 0);
surfaceTexture = new SurfaceTexture(surfaceId[0]);
surfaceTexture.setDefaultBufferSize(getWidth(), getHeight());
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
@ -142,10 +168,16 @@ public class SurfaceFilterRender extends BaseObjectFilterRender {
surface.release();
}
/**
* This texture must be renderer using an api called on main thread to avoid possible errors
*/
public SurfaceTexture getSurfaceTexture() {
return surfaceTexture;
}
/**
* This surface must be renderer using an api called on main thread to avoid possible errors
*/
public Surface getSurface() {
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;
import android.graphics.Typeface;
@ -13,6 +29,11 @@ import com.pedro.encoder.utils.gl.TextStreamObject;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class TextObjectFilterRender extends BaseObjectFilterRender {
private String text;
private float textSize;
private int textColor;
private Typeface typeface;
public TextObjectFilterRender() {
super();
streamObject = new TextStreamObject();
@ -31,7 +52,27 @@ public class TextObjectFilterRender extends BaseObjectFilterRender {
}
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);
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;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.media.CamcorderProfile;
@ -10,9 +27,11 @@ import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import com.pedro.encoder.Frame;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -39,10 +58,11 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
private GetCameraData getCameraData;
private boolean running = false;
private boolean lanternEnable = false;
private boolean videoStabilizationEnable = false;
private boolean autoFocusEnabled = false;
private int cameraSelect;
private boolean isFrontCamera = false;
private CameraHelper.Facing facing = CameraHelper.Facing.BACK;
private boolean isPortrait = false;
private int cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
private Context context;
//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> previewSizeFront;
private float distance;
private CameraCallbacks cameraCallbacks;
private final int focusAreaSize = 100;
//Face detector
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;
public Camera1ApiManager(SurfaceView surfaceView, GetCameraData getCameraData) {
@ -106,23 +131,36 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
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) {
int facing = cameraFacing == CameraHelper.Facing.BACK ? Camera.CameraInfo.CAMERA_FACING_BACK
: Camera.CameraInfo.CAMERA_FACING_FRONT;
this.width = width;
this.height = height;
this.fps = fps;
this.cameraFacing = facing;
cameraSelect =
facing == Camera.CameraInfo.CAMERA_FACING_BACK ? selectCameraBack() : selectCameraFront();
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) {
CameraHelper.Facing facing =
cameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK ? CameraHelper.Facing.BACK
: CameraHelper.Facing.FRONT;
start(facing, width, height, fps);
start(cameraSelect, width, height, fps);
}
private void start() {
@ -134,23 +172,27 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
camera = Camera.open(cameraSelect);
Camera.CameraInfo info = new Camera.CameraInfo();
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
== Configuration.ORIENTATION_PORTRAIT;
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(width, height);
parameters.setPreviewFormat(imageFormat);
int[] range = adaptFpsRange(fps, parameters.getSupportedPreviewFpsRange());
Log.i(TAG, "fps: " + range[0] + " - " + range[1]);
parameters.setPreviewFpsRange(range[0], range[1]);
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 {
parameters.setFocusMode(supportedFocusModes.get(0));
autoFocusEnabled = false;
}
}
camera.setParameters(parameters);
@ -168,8 +210,13 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
}
camera.startPreview();
running = true;
if (cameraCallbacks != null) {
cameraCallbacks.onCameraOpened();
cameraCallbacks.onCameraChanged(facing);
}
Log.i(TAG, width + "X" + height);
} catch (IOException e) {
if (cameraCallbacks != null) cameraCallbacks.onCameraError(e.getMessage());
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) {
try {
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() {
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);
for (int[] range : fpsRanges) {
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) {
closestRange = range;
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
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);
}
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
* constant values
@ -335,8 +481,8 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
}
}
public boolean isFrontCamera() {
return isFrontCamera;
public CameraHelper.Facing getCameraFacing() {
return facing;
}
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");
}
stop();
cameraFacing = cameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK
? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
start();
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() {
List<Camera.Size> previews;
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"/>
*/
@ -410,6 +585,76 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
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() {
if (camera != null) {
@ -427,11 +672,20 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
}
}
public void enableFaceDetection(FaceDetectorCallback faceDetectorCallback) {
if (camera != null) {
this.faceDetectorCallback = faceDetectorCallback;
camera.setFaceDetectionListener(this);
camera.startFaceDetection();
public boolean enableFaceDetection(FaceDetectorCallback faceDetectorCallback) {
try {
if (camera != null) {
this.faceDetectorCallback = faceDetectorCallback;
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() {
return faceDetectorCallback != null;
}
@Override
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;
import android.content.Context;
import android.os.Build;
import android.util.Range;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.RequiresApi;
/**
* Created by pedro on 17/12/18.
*/
@ -54,6 +74,20 @@ public class CameraHelper {
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 {
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;
/**

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;
/**

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;
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;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -18,6 +37,8 @@ public class CodecUtil {
public static final String H264_MIME = "video/avc";
public static final String H265_MIME = "video/hevc";
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 {
FIRST_COMPATIBLE_FOUND, SOFTWARE, HARDWARE
@ -27,110 +48,134 @@ public class CodecUtil {
List<MediaCodecInfo> mediaCodecInfoList = getAllCodecs(false);
List<String> infos = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
String info = "----------------\n";
info += "Name: " + mediaCodecInfo.getName() + "\n";
StringBuilder info = new StringBuilder("----------------\n");
info.append("Name: ")
.append(mediaCodecInfo.getName())
.append("\n");
for (String type : mediaCodecInfo.getSupportedTypes()) {
info += "Type: " + type + "\n";
info.append("Type: ")
.append(type)
.append("\n");
MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(type);
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()) {
info += "----- Encoder info -----\n";
info.append("----- Encoder info -----\n");
MediaCodecInfo.EncoderCapabilities encoderCapabilities = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
encoderCapabilities = codecCapabilities.getEncoderCapabilities();
info += "Complexity range: "
+ encoderCapabilities.getComplexityRange().getLower()
+ " - "
+ encoderCapabilities.getComplexityRange().getUpper()
+ "\n";
info.append("Complexity range: ")
.append(encoderCapabilities.getComplexityRange().getLower())
.append(" - ")
.append(encoderCapabilities.getComplexityRange().getUpper())
.append("\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
info += "Quality range: "
+ encoderCapabilities.getQualityRange().getLower()
+ " - "
+ encoderCapabilities.getQualityRange().getUpper()
+ "\n";
info.append("Quality range: ")
.append(encoderCapabilities.getQualityRange().getLower())
.append(" - ")
.append(encoderCapabilities.getQualityRange().getUpper())
.append("\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
info += "CBR supported: " + encoderCapabilities.isBitrateModeSupported(
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) + "\n";
info += "VBR supported: " + encoderCapabilities.isBitrateModeSupported(
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) + "\n";
info += "CQ supported: " + encoderCapabilities.isBitrateModeSupported(
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ) + "\n";
info.append("CBR supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR))
.append("\n")
.append("VBR supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR))
.append("\n")
.append("CQ supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ))
.append("\n");
}
info += "----- -----\n";
info.append("----- -----\n");
} else {
info += "----- Decoder info -----\n";
info += "----- -----\n";
info.append("----- Decoder info -----\n")
.append("----- -----\n");
}
if (codecCapabilities.colorFormats != null && codecCapabilities.colorFormats.length > 0) {
info += "----- Video info -----\n";
info += "Supported colors: \n";
for (int color : codecCapabilities.colorFormats) info += color + "\n";
info.append("----- Video info -----\n")
.append("Supported colors: \n");
for (int color : codecCapabilities.colorFormats)
info.append(color)
.append("\n");
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;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoCapabilities = codecCapabilities.getVideoCapabilities();
info += "Bitrate range: "
+ videoCapabilities.getBitrateRange().getLower()
+ " - "
+ videoCapabilities.getBitrateRange().getUpper()
+ "\n";
info += "Frame rate range: "
+ videoCapabilities.getSupportedFrameRates().getLower()
+ " - "
+ videoCapabilities.getSupportedFrameRates().getUpper()
+ "\n";
info += "Width range: "
+ videoCapabilities.getSupportedWidths().getLower()
+ " - "
+ videoCapabilities.getSupportedWidths().getUpper()
+ "\n";
info += "Height range: "
+ videoCapabilities.getSupportedHeights().getLower()
+ " - "
+ videoCapabilities.getSupportedHeights().getUpper()
+ "\n";
info.append("Bitrate range: ")
.append(videoCapabilities.getBitrateRange().getLower())
.append(" - ")
.append(videoCapabilities.getBitrateRange().getUpper())
.append("\n")
.append("Frame rate range: ")
.append(videoCapabilities.getSupportedFrameRates().getLower())
.append(" - ")
.append(videoCapabilities.getSupportedFrameRates().getUpper())
.append("\n")
.append("Width range: ")
.append(videoCapabilities.getSupportedWidths().getLower())
.append(" - ")
.append(videoCapabilities.getSupportedWidths().getUpper())
.append("\n")
.append("Height range: ")
.append(videoCapabilities.getSupportedHeights().getLower())
.append(" - ")
.append(videoCapabilities.getSupportedHeights().getUpper())
.append("\n");
}
info += "----- -----\n";
info.append("----- -----\n");
} else {
info += "----- Audio info -----\n";
info.append("----- Audio info -----\n");
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) {
MediaCodecInfo.AudioCapabilities audioCapabilities =
codecCapabilities.getAudioCapabilities();
info += "Bitrate range: "
+ audioCapabilities.getBitrateRange().getLower()
+ " - "
+ audioCapabilities.getBitrateRange().getUpper()
+ "\n";
info += "Channels supported: " + audioCapabilities.getMaxInputChannelCount() + "\n";
info.append("Bitrate range: ")
.append(audioCapabilities.getBitrateRange().getLower())
.append(" - ")
.append(audioCapabilities.getBitrateRange().getUpper())
.append("\n")
.append("Channels supported: ")
.append(audioCapabilities.getMaxInputChannelCount())
.append("\n");
try {
if (audioCapabilities.getSupportedSampleRates() != null
&& audioCapabilities.getSupportedSampleRates().length > 0) {
info += "Supported sample rate: \n";
for (int sr : audioCapabilities.getSupportedSampleRates()) info += sr + "\n";
info.append("Supported sample rate: \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) {
info += "Max instances: " + codecCapabilities.getMaxSupportedInstances() + "\n";
info.append("Max instances: ")
.append(codecCapabilities.getMaxSupportedInstances())
.append("\n");
}
}
info += "----------------\n";
infos.add(info);
info.append("----------------\n");
infos.add(info.toString());
}
return infos;
}
@ -151,18 +196,30 @@ public class CodecUtil {
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> mediaCodecInfoHardware = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfoHardwareCBR = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isHardwareAccelerated(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;
}
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> mediaCodecInfoHardware = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
@ -173,18 +230,30 @@ public class CodecUtil {
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> mediaCodecInfoSoftware = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfoSoftwareCBR = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isSoftwareOnly(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;
}
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> mediaCodecInfoSoftware = new ArrayList<>();
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) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
@ -215,9 +284,26 @@ public class CodecUtil {
return mediaCodecInfoList;
}
/**
* choose the video encoder by mime.
*/
public static List<MediaCodecInfo> getAllEncoders(String mime, boolean hardwarePriority, boolean cbrPriority) {
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) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfos = getAllCodecs(true);
@ -235,6 +321,17 @@ public class CodecUtil {
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
* https://github.com/google/ExoPlayer/commit/48555550d7fcf6953f2382466818c74092b26355
*/
@ -252,7 +349,9 @@ public class CodecUtil {
*/
private static boolean isSoftwareOnly(MediaCodecInfo mediaCodecInfo) {
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();
if (name.startsWith("arc.")) { // App Runtime for Chrome (ARC) codecs
@ -267,6 +366,16 @@ public class CodecUtil {
|| (!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.
*
@ -279,11 +388,27 @@ public class CodecUtil {
*/
private static List<MediaCodecInfo> filterBrokenCodecs(List<MediaCodecInfo> codecs) {
List<MediaCodecInfo> listFilter = new ArrayList<>();
List<MediaCodecInfo> listLowPriority = new ArrayList<>();
List<MediaCodecInfo> listUltraLowPriority = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : codecs) {
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;
}
@ -291,6 +416,26 @@ public class CodecUtil {
* For now, none broken codec reported.
*/
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;
}
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