Compare commits
14 Commits
2073a12bbb
...
53f16d1ad7
Author | SHA1 | Date |
---|---|---|
Schoumi | 53f16d1ad7 | |
Schoumi | d93f4b5c84 | |
Mi Klo | 58f68d1797 | |
Mi Klo | ccf0eb80db | |
Mi Klo | cc782668c5 | |
Felix Tymcik | 1839e91896 | |
J. Lavoie | cfee1af487 | |
Schoumi | 06ab71610f | |
Schoumi | cfffa2e418 | |
Schoumi | 3abf6c3554 | |
Schoumi | 556bca4622 | |
Schoumi | 82dae368e0 | |
Schoumi | b0dace54d7 | |
Schoumi | 1709bc3688 |
|
@ -1,9 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: exec
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
commands:
|
|
||||||
- bash ./gradlew assembleDebug
|
|
|
@ -19,7 +19,6 @@
|
||||||
<option value="$PROJECT_DIR$/rtsp" />
|
<option value="$PROJECT_DIR$/rtsp" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -26,5 +26,10 @@
|
||||||
<option name="name" value="maven" />
|
<option name="name" value="maven" />
|
||||||
<option name="url" value="https://jitpack.io" />
|
<option name="url" value="https://jitpack.io" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenRepo" />
|
||||||
|
<option name="name" value="MavenRepo" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -5,6 +5,13 @@
|
||||||
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
|
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
|
||||||
</configurations>
|
</configurations>
|
||||||
</component>
|
</component>
|
||||||
|
<component name="DesignSurface">
|
||||||
|
<option name="filePathToZoomLevelMap">
|
||||||
|
<map>
|
||||||
|
<entry key="../../../../../layout/custom_preview.xml" value="0.14448441247002397" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -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
|
|
@ -5,13 +5,13 @@ apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 32
|
||||||
buildToolsVersion "31.0.0"
|
buildToolsVersion "32.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "fr.mobdev.peertubelive"
|
applicationId "fr.mobdev.peertubelive"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 32
|
||||||
versionCode 2
|
versionCode 2
|
||||||
versionName "1.1"
|
versionName "1.1"
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ android {
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
dataBinding true
|
dataBinding true
|
||||||
}
|
}
|
||||||
|
namespace 'fr.mobdev.peertubelive'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="fr.mobdev.peertubelive">
|
|
||||||
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
|
@ -32,12 +32,14 @@ import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import com.pedro.encoder.input.video.CameraHelper
|
import com.pedro.encoder.input.video.CameraHelper
|
||||||
|
import com.pedro.rtmp.utils.ConnectCheckerRtmp
|
||||||
import com.pedro.rtplibrary.rtmp.RtmpCamera2
|
import com.pedro.rtplibrary.rtmp.RtmpCamera2
|
||||||
import net.ossrs.rtmp.ConnectCheckerRtmp
|
|
||||||
import fr.mobdev.peertubelive.R
|
import fr.mobdev.peertubelive.R
|
||||||
import fr.mobdev.peertubelive.databinding.StreamBinding
|
import fr.mobdev.peertubelive.databinding.StreamBinding
|
||||||
import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA
|
import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA
|
||||||
import fr.mobdev.peertubelive.objects.StreamData
|
import fr.mobdev.peertubelive.objects.StreamData
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class StreamActivity : AppCompatActivity() {
|
class StreamActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -50,10 +52,11 @@ class StreamActivity : AppCompatActivity() {
|
||||||
private var surfaceInit: Boolean = false
|
private var surfaceInit: Boolean = false
|
||||||
private var permissionGiven: Boolean = false
|
private var permissionGiven: Boolean = false
|
||||||
private var streamIsActive: Boolean = false
|
private var streamIsActive: Boolean = false
|
||||||
private var screenOrientation: Int = 0
|
private var screenOrientation: Int = -1
|
||||||
private var lastScreenOrientation: Int = 0
|
private var lastScreenOrientation: Int = 0
|
||||||
private var orientationCounter: Int = 0
|
private var rotationIsEnabled: Boolean = true
|
||||||
private var rotationIsLanternEnabled: Boolean = true
|
|
||||||
|
private var orientationTimer: Timer = Timer()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BACKGROUND :Int = 1
|
const val BACKGROUND :Int = 1
|
||||||
|
@ -69,46 +72,11 @@ class StreamActivity : AppCompatActivity() {
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
binding = DataBindingUtil.setContentView(this, R.layout.stream)
|
binding = DataBindingUtil.setContentView(this, R.layout.stream)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){
|
orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){
|
||||||
override fun onOrientationChanged(orientation: Int) {
|
override fun onOrientationChanged(orientation: Int) {
|
||||||
if(orientation < 0 || !rotationIsLanternEnabled)
|
handlerOrientation(orientation)
|
||||||
return
|
|
||||||
var localOrientation: Int
|
|
||||||
localOrientation = when (orientation) {
|
|
||||||
in 45..135 -> {
|
|
||||||
90
|
|
||||||
}
|
|
||||||
in 135..225 -> {
|
|
||||||
180
|
|
||||||
}
|
|
||||||
in 225..315 -> {
|
|
||||||
270
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(localOrientation != lastScreenOrientation) {
|
|
||||||
lastScreenOrientation = localOrientation
|
|
||||||
orientationCounter = 0
|
|
||||||
} else {
|
|
||||||
orientationCounter++
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastScreenOrientation != screenOrientation && orientationCounter > 30) {
|
|
||||||
screenOrientation = lastScreenOrientation
|
|
||||||
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
|
|
||||||
|
|
||||||
if (screenOrientation == 90) {
|
|
||||||
localOrientation = 270
|
|
||||||
} else if(screenOrientation == 270) {
|
|
||||||
localOrientation = 90
|
|
||||||
}
|
|
||||||
binding.flash.rotation = localOrientation.toFloat()
|
|
||||||
binding.muteMicro.rotation = localOrientation.toFloat()
|
|
||||||
binding.switchCamera.rotation = localOrientation.toFloat()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
orientationEventListener.enable()
|
orientationEventListener.enable()
|
||||||
|
@ -144,14 +112,11 @@ class StreamActivity : AppCompatActivity() {
|
||||||
binding.flash.visibility = View.GONE
|
binding.flash.visibility = View.GONE
|
||||||
|
|
||||||
binding.rotation.setOnClickListener {
|
binding.rotation.setOnClickListener {
|
||||||
if (rotationIsLanternEnabled) {
|
if (rotationIsEnabled)
|
||||||
rotationIsLanternEnabled = !rotationIsLanternEnabled
|
|
||||||
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
|
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
rotationIsLanternEnabled = !rotationIsLanternEnabled
|
|
||||||
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
|
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
|
||||||
}
|
rotationIsEnabled = !rotationIsEnabled
|
||||||
}
|
}
|
||||||
binding.rotation.visibility = View.GONE
|
binding.rotation.visibility = View.GONE
|
||||||
binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
|
binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
|
||||||
|
@ -260,6 +225,52 @@ class StreamActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handlerOrientation(orientation: Int) {
|
||||||
|
if(orientation < 0 || !rotationIsEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var localOrientation: Int
|
||||||
|
localOrientation = when (orientation) {
|
||||||
|
in 45..135 -> {
|
||||||
|
90
|
||||||
|
}
|
||||||
|
in 135..225 -> {
|
||||||
|
180
|
||||||
|
}
|
||||||
|
in 225..315 -> {
|
||||||
|
270
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(localOrientation != lastScreenOrientation) {
|
||||||
|
lastScreenOrientation = localOrientation
|
||||||
|
orientationTimer.cancel()
|
||||||
|
orientationTimer.purge()
|
||||||
|
orientationTimer = Timer()
|
||||||
|
orientationTimer.schedule(object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
if (lastScreenOrientation != screenOrientation) {
|
||||||
|
screenOrientation = lastScreenOrientation
|
||||||
|
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
|
||||||
|
|
||||||
|
if (screenOrientation == 90) {
|
||||||
|
localOrientation = 270
|
||||||
|
} else if(screenOrientation == 270) {
|
||||||
|
localOrientation = 90
|
||||||
|
}
|
||||||
|
binding.flash.rotation = localOrientation.toFloat()
|
||||||
|
binding.muteMicro.rotation = localOrientation.toFloat()
|
||||||
|
binding.switchCamera.rotation = localOrientation.toFloat()
|
||||||
|
binding.rotation.rotation = localOrientation.toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startStream() {
|
private fun startStream() {
|
||||||
streamIsActive = true
|
streamIsActive = true
|
||||||
binding.permissionInfo.visibility = View.GONE
|
binding.permissionInfo.visibility = View.GONE
|
||||||
|
@ -271,6 +282,10 @@ class StreamActivity : AppCompatActivity() {
|
||||||
binding.flash.visibility = View.VISIBLE
|
binding.flash.visibility = View.VISIBLE
|
||||||
|
|
||||||
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
|
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
|
||||||
|
override fun onConnectionStartedRtmp(rtmpUrl: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun onConnectionSuccessRtmp() {
|
override fun onConnectionSuccessRtmp() {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
|
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
|
||||||
|
@ -347,7 +362,8 @@ class StreamActivity : AppCompatActivity() {
|
||||||
|
|
||||||
//start stream
|
//start stream
|
||||||
|
|
||||||
if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),false,CameraHelper.getCameraOrientation(this))) {
|
if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),CameraHelper.getCameraOrientation(this),true)) {
|
||||||
|
println("peertubeurl "+streamData.url+"/"+streamData.key)
|
||||||
rtmpCamera2.startStream(streamData.url+"/"+streamData.key)
|
rtmpCamera2.startStream(streamData.url+"/"+streamData.key)
|
||||||
} else {
|
} else {
|
||||||
/**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */
|
/**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:id="@+id/surfaceView"
|
android:id="@+id/surfaceView"
|
||||||
app:keepAspectRatio="true"
|
app:keepAspectRatio="true"
|
||||||
app:aspectRatioMode="adjust_rotate"
|
app:aspectRatioMode="adjust"
|
||||||
app:AAEnabled="false"
|
app:AAEnabled="false"
|
||||||
app:numFilters="1"
|
app:numFilters="1"
|
||||||
app:isFlipHorizontal="false"
|
app:isFlipHorizontal="false"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="no_instance">Kein Konto registriert. Um ein Peertube-Konto hinzuzufügen, klicken Sie auf das + in der oberen Leiste</string>
|
<string name="no_instance">Kein Konto registriert. Um ein Peertube-Konto hinzuzufügen, klicke auf das + in der oberen Leiste</string>
|
||||||
<string name="german">Deutsch</string>
|
<string name="german">Deutsch</string>
|
||||||
<string name="arabic">Arabisch</string>
|
<string name="arabic">Arabisch</string>
|
||||||
<string name="food">Essen</string>
|
<string name="food">Essen</string>
|
||||||
|
@ -44,14 +44,14 @@
|
||||||
<string name="yes">Ja</string>
|
<string name="yes">Ja</string>
|
||||||
<string name="cancel">Abbrechen</string>
|
<string name="cancel">Abbrechen</string>
|
||||||
<string name="choose_channel">Kanal</string>
|
<string name="choose_channel">Kanal</string>
|
||||||
<string name="stop_reason">Ihr Live ist beendet</string>
|
<string name="stop_reason">Dein Livestream ist beendet</string>
|
||||||
<string name="background_reason">Ihr Live ist beendet, weil die Anwendung in den Hintergrund gegangen ist</string>
|
<string name="background_reason">Dein Livestream ist beendet, weil die Anwendung in den Hintergrund gegangen ist</string>
|
||||||
<string name="back_reason">Ihr Leben wurde beendet, nachdem Sie die Zurück-Taste gedrückt haben</string>
|
<string name="back_reason">Dein Livestream wurde beendet, nachdem Du die Zurück-Taste gedrückt hast</string>
|
||||||
<string name="tags_rules">Maximal 5 Markierungen, jeweils zwischen 2 und 30 Zeichen, durch Komma getrennt</string>
|
<string name="tags_rules">Maximal 5 Markierungen, jeweils zwischen 2 und 30 Zeichen, durch Komma getrennt</string>
|
||||||
<string name="delete_account">Sind Sie sicher, dass Sie das Konto %s, das mit dem Server %s verbunden ist, löschen möchten\?</string>
|
<string name="delete_account">Bist du sicher, dass du das Konto %s, das mit dem Server %s verbunden ist, löschen möchtest\?</string>
|
||||||
<string name="permissions">Wir benötigen den Zugriff auf die Kamera und das Mikrofon, um live zu streamen. Zum Ändern der Berechtigungseinstellungen klicken Sie unten</string>
|
<string name="permissions">Wir benötigen den Zugriff auf Kamera und Mikrofon, um live zu streamen. Zum Ändern der Berechtigungseinstellungen klicke bitte unten.</string>
|
||||||
<string name="try_connect">Warten auf Verbindung</string>
|
<string name="try_connect">Warte auf Verbindung</string>
|
||||||
<string name="loading_channels">Ihr Kanalliste wird geladen</string>
|
<string name="loading_channels">Deine Kanalliste wird geladen</string>
|
||||||
<string name="account_exist">Dieses Konto existiert bereits</string>
|
<string name="account_exist">Dieses Konto existiert bereits</string>
|
||||||
<string name="malformed_instance_error">Instanz sollte eine gültige URL sein</string>
|
<string name="malformed_instance_error">Instanz sollte eine gültige URL sein</string>
|
||||||
<string name="password_error">Passwort kann nicht leer sein</string>
|
<string name="password_error">Passwort kann nicht leer sein</string>
|
||||||
|
@ -63,4 +63,219 @@
|
||||||
<string name="unknwon_error">Unbekannter Fehler</string>
|
<string name="unknwon_error">Unbekannter Fehler</string>
|
||||||
<string name="network_error">Keine Internetverbindung gefunden</string>
|
<string name="network_error">Keine Internetverbindung gefunden</string>
|
||||||
<string name="app_name">Peertube Live</string>
|
<string name="app_name">Peertube Live</string>
|
||||||
|
<string name="bashkir">Baschkirisch</string>
|
||||||
|
<string name="british_sign_language">Britische Gebärdensprache</string>
|
||||||
|
<string name="bislama">Bislama</string>
|
||||||
|
<string name="tibetan">Tibetisch</string>
|
||||||
|
<string name="bosnian">Bosnisch</string>
|
||||||
|
<string name="breton">Bretonisch</string>
|
||||||
|
<string name="bulgarian">Bulgarisch</string>
|
||||||
|
<string name="brazilian_sign_language">Brasilianische Gebärdensprache</string>
|
||||||
|
<string name="chechen">Tschetschenisch</string>
|
||||||
|
<string name="chuvash">Tschuwaschisch</string>
|
||||||
|
<string name="danish">Dänisch</string>
|
||||||
|
<string name="dzongkha">Dzongkha</string>
|
||||||
|
<string name="greek">Griechisch</string>
|
||||||
|
<string name="estonian">Estnisch</string>
|
||||||
|
<string name="basque">Baskisch</string>
|
||||||
|
<string name="persian">Persisch</string>
|
||||||
|
<string name="fijian">Fidschianisch</string>
|
||||||
|
<string name="finnish">Finnisch</string>
|
||||||
|
<string name="french">Französisch</string>
|
||||||
|
<string name="western_frisian">Westfriesisch</string>
|
||||||
|
<string name="japanese_sign_language">Japanische Gebärdensprache</string>
|
||||||
|
<string name="kirghiz">Kirgisisch</string>
|
||||||
|
<string name="norwegian_nynorsk">Norwegisch (Nynorsk)</string>
|
||||||
|
<string name="norwegian_bokmål">Norwegisch (Bokmål)</string>
|
||||||
|
<string name="polish">Polnisch</string>
|
||||||
|
<string name="portuguese">Portugiesisch</string>
|
||||||
|
<string name="pushto">Paschtu</string>
|
||||||
|
<string name="quechua">Quechua</string>
|
||||||
|
<string name="spanish">Spanisch</string>
|
||||||
|
<string name="albanian">Albanisch</string>
|
||||||
|
<string name="tamil">Tamilisch</string>
|
||||||
|
<string name="uzbek">Usbekisch</string>
|
||||||
|
<string name="vietnamese">Vietnamesisch</string>
|
||||||
|
<string name="walloon">Wallonisch</string>
|
||||||
|
<string name="zhuang">Zhuang</string>
|
||||||
|
<string name="bync">Namensnennung - nicht kommerziell</string>
|
||||||
|
<string name="bysa">Namensnennung - Weitergabe unter gleichen Bedingungen</string>
|
||||||
|
<string name="byncnd">Namensnennung - Nicht kommerziell - Keine Derivate</string>
|
||||||
|
<string name="public_domain">Gemeingut Widmung</string>
|
||||||
|
<string name="byncsa">Namensnennung - Nicht kommerziell - Weitergabe unter gleichen Bedingungen</string>
|
||||||
|
<string name="privacy_public">Öffentlich</string>
|
||||||
|
<string name="unlisted">Ungelistet</string>
|
||||||
|
<string name="privacy_private">Privat</string>
|
||||||
|
<string name="internal">Intern</string>
|
||||||
|
<string name="yiddish">Jiddisch</string>
|
||||||
|
<string name="cornish">Kornisch</string>
|
||||||
|
<string name="corsican">Korsisch</string>
|
||||||
|
<string name="welsh">Walisisch</string>
|
||||||
|
<string name="latvian">Lettisch</string>
|
||||||
|
<string name="cree">Cree</string>
|
||||||
|
<string name="faroese">Färöisch</string>
|
||||||
|
<string name="english">Englisch</string>
|
||||||
|
<string name="esperanto">Esperanto</string>
|
||||||
|
<string name="ewe">Ewe</string>
|
||||||
|
<string name="scottish_gaelic">Schottisch-Gälisch</string>
|
||||||
|
<string name="irish">Irisch</string>
|
||||||
|
<string name="gujarati">Gujarati</string>
|
||||||
|
<string name="haitian">Haitianisch</string>
|
||||||
|
<string name="serbo_croatian">Serbo-kroatisch</string>
|
||||||
|
<string name="hungarian">Ungarisch</string>
|
||||||
|
<string name="armenian">Armenisch</string>
|
||||||
|
<string name="icelandic">Isländisch</string>
|
||||||
|
<string name="javanese">Javanisch</string>
|
||||||
|
<string name="kabyle">Kabylisch</string>
|
||||||
|
<string name="georgian">Georgisch</string>
|
||||||
|
<string name="korean">Koreanisch</string>
|
||||||
|
<string name="norwegian">Norwegisch</string>
|
||||||
|
<string name="panjabi">Panjabi</string>
|
||||||
|
<string name="romansh">Rätoromanisch</string>
|
||||||
|
<string name="romanian">Rumänisch</string>
|
||||||
|
<string name="russian">Russisch</string>
|
||||||
|
<string name="sinhala">Singhalesisch</string>
|
||||||
|
<string name="slovak">Slowakisch</string>
|
||||||
|
<string name="northern_sami">Nordsamisch</string>
|
||||||
|
<string name="serbian">Serbisch</string>
|
||||||
|
<string name="tigrinya">Tigrinya</string>
|
||||||
|
<string name="turkish">Türkisch</string>
|
||||||
|
<string name="uighur">Uigurisch</string>
|
||||||
|
<string name="ukrainian">Ukrainisch</string>
|
||||||
|
<string name="save_replay_info">Wenn Sie diese Option aktivieren, wird Ihre Live-Sendung beendet, wenn Sie Ihr Videokontingent überschreiten</string>
|
||||||
|
<string name="lock_reason">Dein Livestream wurde beendet, weil das Telefon gesperrt wurde</string>
|
||||||
|
<string name="zulu">Zulu</string>
|
||||||
|
<string name="yoruba">Yoruba</string>
|
||||||
|
<string name="bengali">Bengalisch</string>
|
||||||
|
<string name="german_sign_language">Deutsche Gebärdensprache</string>
|
||||||
|
<string name="japanese">Japanisch</string>
|
||||||
|
<string name="lithuanian">Litauisch</string>
|
||||||
|
<string name="luxembourgish">Luxemburgisch</string>
|
||||||
|
<string name="dutch">Niederländisch</string>
|
||||||
|
<string name="by">Namensnennung</string>
|
||||||
|
<string name="bynd">Namensnennung - Keine Derivate</string>
|
||||||
|
<string name="chinese">Chinesisch</string>
|
||||||
|
<string name="american_sign_language">Amerikanische Gebärdensprache</string>
|
||||||
|
<string name="azerbaijani">Aserbaidschanisch</string>
|
||||||
|
<string name="bambara">Bambara</string>
|
||||||
|
<string name="belarusian">Weißrussisch</string>
|
||||||
|
<string name="czech_sign_language">Tschechische Gebärdensprache</string>
|
||||||
|
<string name="chinese_sign_language">Chinesische Gebärdensprache</string>
|
||||||
|
<string name="french_sign_language">Französische Gebärdensprache</string>
|
||||||
|
<string name="hebrew">Hebräisch</string>
|
||||||
|
<string name="croatian">Kroatisch</string>
|
||||||
|
<string name="italian">Italienisch</string>
|
||||||
|
<string name="lojban">Lojban</string>
|
||||||
|
<string name="pakistan_sign_language">Pakistanische Gebärdensprache</string>
|
||||||
|
<string name="tahitian">Tahitianisch</string>
|
||||||
|
<string name="tajik">Tadschikisch</string>
|
||||||
|
<string name="galician">Galizisch</string>
|
||||||
|
<string name="manx">Manx</string>
|
||||||
|
<string name="hausa">Hausa</string>
|
||||||
|
<string name="slovenian">Slowenisch</string>
|
||||||
|
<string name="samoan">Samoanisch</string>
|
||||||
|
<string name="swedish">Schwedisch</string>
|
||||||
|
<string name="russian_sign_language">Russische Gebärdensprache</string>
|
||||||
|
<string name="swedish_sign_language">Schwedische Gebärdensprache</string>
|
||||||
|
<string name="urdu">Urdu</string>
|
||||||
|
<string name="connection">Verbindung</string>
|
||||||
|
<string name="stream_ended">Livestream beendet</string>
|
||||||
|
<string name="creating">Warte auf die Erstellung des Livestreams</string>
|
||||||
|
<string name="stream_resolution">Auflösung des Livestreams</string>
|
||||||
|
<string name="exemple_instance">z.B. peertube.fr, https://peertube.fr</string>
|
||||||
|
<string name="save_replay">Livestream automatisch veröffentlichen sobald dieser beendet wurde</string>
|
||||||
|
<string name="end_stream">Livestream beenden</string>
|
||||||
|
<string name="abkhazian">Abchasisch</string>
|
||||||
|
<string name="afrikaans">Afrikaans</string>
|
||||||
|
<string name="afar">Afar</string>
|
||||||
|
<string name="amharic">Amharisch</string>
|
||||||
|
<string name="aragonese">Aragonesisch</string>
|
||||||
|
<string name="akan">Akan</string>
|
||||||
|
<string name="assamese">Assamesisch</string>
|
||||||
|
<string name="avaric">Awarisch</string>
|
||||||
|
<string name="kotava">Kotava</string>
|
||||||
|
<string name="aymara">Aymara</string>
|
||||||
|
<string name="catalan">Katalanisch</string>
|
||||||
|
<string name="czech">Tschechisch</string>
|
||||||
|
<string name="chamorro">Chamorro</string>
|
||||||
|
<string name="dhivehi">Dhivehi</string>
|
||||||
|
<string name="danish_sign_language">Dänische Zeichensprache</string>
|
||||||
|
<string name="fulah">Fulfulde</string>
|
||||||
|
<string name="guarani">Guaraní</string>
|
||||||
|
<string name="herero">Herero</string>
|
||||||
|
<string name="hindi">Hindi</string>
|
||||||
|
<string name="hiri_motu">Hiri Motu</string>
|
||||||
|
<string name="igbo">Igbo</string>
|
||||||
|
<string name="sichuan_yi">Sichuan Yi</string>
|
||||||
|
<string name="inuktitut">Inuktitut</string>
|
||||||
|
<string name="indonesian">Indonesisch</string>
|
||||||
|
<string name="inupiaq">Inupiaq</string>
|
||||||
|
<string name="kannada">Kannada</string>
|
||||||
|
<string name="kashmiri">Kaschmiri</string>
|
||||||
|
<string name="kazakh">Kasachisch</string>
|
||||||
|
<string name="kalaallisut">Kalaallisut</string>
|
||||||
|
<string name="kanuri">Kanuri</string>
|
||||||
|
<string name="khmer">Khmer</string>
|
||||||
|
<string name="kikuyu">Kikuyu</string>
|
||||||
|
<string name="kinyarwanda">Ruandisch</string>
|
||||||
|
<string name="kongo">Kongo</string>
|
||||||
|
<string name="komi">Komi</string>
|
||||||
|
<string name="kuanyama">Kwanyama</string>
|
||||||
|
<string name="kurdish">Kurdisch</string>
|
||||||
|
<string name="lao">Lao</string>
|
||||||
|
<string name="limburgan">Limburgisch</string>
|
||||||
|
<string name="lingala">Lingala</string>
|
||||||
|
<string name="luba_katanga">Luba-Katanga</string>
|
||||||
|
<string name="ganda">Ganda</string>
|
||||||
|
<string name="marshallese">Marshallesisch</string>
|
||||||
|
<string name="malayalam">Malayalam</string>
|
||||||
|
<string name="marathi">Marathi</string>
|
||||||
|
<string name="macedonian">Mazedonisch</string>
|
||||||
|
<string name="maltese">Maltesisch</string>
|
||||||
|
<string name="mongolian">Mongolisch</string>
|
||||||
|
<string name="malagasy">Madegassisch</string>
|
||||||
|
<string name="maori">Maori</string>
|
||||||
|
<string name="malay_macrolanguage">Malaiisch (Makrosprache)</string>
|
||||||
|
<string name="burmese">Birmesisch</string>
|
||||||
|
<string name="nauru">Nauruisch</string>
|
||||||
|
<string name="navajo">Navaho</string>
|
||||||
|
<string name="south_ndebele">Süd-Ndebele</string>
|
||||||
|
<string name="north_ndebele">Nord-Ndebele</string>
|
||||||
|
<string name="ndonga">Ndonga</string>
|
||||||
|
<string name="nepali_macrolanguage">Nepali (Makrosprache)</string>
|
||||||
|
<string name="occitan">Okzitanisch</string>
|
||||||
|
<string name="nyanja">Nyanja</string>
|
||||||
|
<string name="oromo">Oromo</string>
|
||||||
|
<string name="ossetian">Ossetisch</string>
|
||||||
|
<string name="oriya_macrolanguage">Odia (Makrosprache)</string>
|
||||||
|
<string name="ojibwa">Ojibwe</string>
|
||||||
|
<string name="saudi_arabian_sign_language">Saudi-Arabische Gebärdensprache</string>
|
||||||
|
<string name="south_african_sign_language">Südafrikanische Gebärdensprache</string>
|
||||||
|
<string name="sango">Sango</string>
|
||||||
|
<string name="rundi">Kirundi</string>
|
||||||
|
<string name="somali">Somali</string>
|
||||||
|
<string name="southern_sotho">Sesotho</string>
|
||||||
|
<string name="sindhi">Sindhi</string>
|
||||||
|
<string name="shona">Shona</string>
|
||||||
|
<string name="sardinian">Sardisch</string>
|
||||||
|
<string name="sundanese">Sundanesisch</string>
|
||||||
|
<string name="swati">Swati</string>
|
||||||
|
<string name="swahili_macrolanguage">Swahili (Makrosprache)</string>
|
||||||
|
<string name="tatar">Tatarisch</string>
|
||||||
|
<string name="telugu">Telugu</string>
|
||||||
|
<string name="tagalog">Tagalog</string>
|
||||||
|
<string name="klingon">Klingonisch</string>
|
||||||
|
<string name="thai">Thai</string>
|
||||||
|
<string name="turkmen">Turkmenisch</string>
|
||||||
|
<string name="tsonga">Tsonga</string>
|
||||||
|
<string name="tswana">Tswana</string>
|
||||||
|
<string name="tonga_tonga_islands">Tonga (Tonga-Inseln)</string>
|
||||||
|
<string name="twi">Twi</string>
|
||||||
|
<string name="wolof">Wolof</string>
|
||||||
|
<string name="xhosa">Xhosa</string>
|
||||||
|
<string name="venda">Venda</string>
|
||||||
|
<string name="ask_end_stream">Willst du den Livestream beenden\?</string>
|
||||||
|
<string name="live_disabled">Auf dieser Instanz sind Live-Streams deaktiviert</string>
|
||||||
|
<string name="go_live">Starte Livestream</string>
|
||||||
|
<string name="network_reason">Ihr Livestream wurde beendet, aufgrund eines Netzwerkproblems</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="unknwon_error">Tuccḍa tarussint</string>
|
|
||||||
<string name="app_name">PeerTube Live</string>
|
|
||||||
<string name="network_error">Ulac tuqqna Internet ittwafen</string>
|
|
||||||
</resources>
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources></resources>
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">PeerTube Live</string>
|
||||||
|
<string name="network_error">Nie znaleziono połączenia z Internetem</string>
|
||||||
|
<string name="unknwon_error">Nieznany błąd</string>
|
||||||
|
<string name="unknown_host">Nieznany Host</string>
|
||||||
|
<string name="json_error">Błąd JSON</string>
|
||||||
|
<string name="stream_title_error">Tytuł nie może być pusty</string>
|
||||||
|
<string name="instance_error">Instancja nie może być pusta</string>
|
||||||
|
<string name="live_disabled">Ta instancja ma wyłączone transmisje na żywo.</string>
|
||||||
|
<string name="stream_ended">Transmisja na żywo zakończona</string>
|
||||||
|
<string name="malformed_instance_error">Instancja musi mieć poprawny adres URL</string>
|
||||||
|
<string name="account_exist">To konto już istnieje</string>
|
||||||
|
<string name="loading_channels">Pobieranie listy kanałów</string>
|
||||||
|
<string name="try_connect">Oczekiwanie na połączenie</string>
|
||||||
|
<string name="ask_end_stream">Czy chcesz zatrzymać transmisję\?</string>
|
||||||
|
<string name="stop_reason">Twoja transmisja na żywo zakończyła się</string>
|
||||||
|
<string name="back_reason">Twoja transmisja na żywo zakończyła się po wciśnięciu przycisku wstecz</string>
|
||||||
|
<string name="choose_channel">Kanał</string>
|
||||||
|
<string name="go_live">Rozpocznij transmisję na żywo</string>
|
||||||
|
<string name="goto_permissions">Przejdź do ustawień</string>
|
||||||
|
<string name="connect">Połącz</string>
|
||||||
|
<string name="stream_title">Tytuł</string>
|
||||||
|
<string name="stream_category">Kategoria</string>
|
||||||
|
<string name="stream_privacy">Prywatność</string>
|
||||||
|
<string name="stream_language">Język</string>
|
||||||
|
<string name="stream_licence">Licencja</string>
|
||||||
|
<string name="advanced_settings">▶ Ustawienia zaawansowane</string>
|
||||||
|
<string name="advanced_settings_expand">▼ Ustawienia zaawansowane</string>
|
||||||
|
<string name="add_instance">Dodaj to konto</string>
|
||||||
|
<string name="connection">Połączenie</string>
|
||||||
|
<string name="username">Nazwa użytkownika</string>
|
||||||
|
<string name="password">Hasło</string>
|
||||||
|
<string name="instance">Instancja</string>
|
||||||
|
<string name="delete_account_title">Usuń to konto</string>
|
||||||
|
<string name="download_enabled">Zezwól na pobieranie</string>
|
||||||
|
<string name="nsfw">Zawiera treści wrażliwe</string>
|
||||||
|
<string name="tags">Tagi</string>
|
||||||
|
<string name="description">Opis</string>
|
||||||
|
<string name="save_replay">Automatycznie publikuj powtórkę, gdy transmisja zakończy się</string>
|
||||||
|
<string name="end_stream">Zatrzymaj transmisję na żywo</string>
|
||||||
|
<string name="creating">Czekaj na utworzenie transmisji</string>
|
||||||
|
<string name="stream_resolution">Jakość transmisji na żywo</string>
|
||||||
|
<string name="music">Muzyka</string>
|
||||||
|
<string name="films">Filmy</string>
|
||||||
|
<string name="vehicles">Pojazdy</string>
|
||||||
|
<string name="art">Sztuka</string>
|
||||||
|
<string name="sports">Sport</string>
|
||||||
|
<string name="travels">Podróże</string>
|
||||||
|
<string name="gaming">Gry</string>
|
||||||
|
<string name="people">Ludzie</string>
|
||||||
|
<string name="comedy">Komedia</string>
|
||||||
|
<string name="entertainment">Rozrywka</string>
|
||||||
|
<string name="news_politics">Wiadomości i polityka</string>
|
||||||
|
<string name="how_to">Poradniki</string>
|
||||||
|
<string name="education">Edukacja</string>
|
||||||
|
<string name="activism">Aktywizm</string>
|
||||||
|
<string name="science_tech">Nauka i technologia</string>
|
||||||
|
<string name="food">Jedzenie</string>
|
||||||
|
<string name="afar">Afar</string>
|
||||||
|
<string name="abkhazian">Abchazki</string>
|
||||||
|
<string name="afrikaans">Afrikaans</string>
|
||||||
|
<string name="amharic">Amharski</string>
|
||||||
|
<string name="aragonese">Aragoński</string>
|
||||||
|
<string name="assamese">Assamski</string>
|
||||||
|
<string name="avaric">Avaric</string>
|
||||||
|
<string name="kotava">Kotava</string>
|
||||||
|
<string name="aymara">Aymara</string>
|
||||||
|
<string name="azerbaijani">Azerski</string>
|
||||||
|
<string name="bashkir">Bashkir</string>
|
||||||
|
<string name="bambara">Bambara</string>
|
||||||
|
<string name="bengali">Bengali</string>
|
||||||
|
<string name="british_sign_language">Brytyjski język migowy</string>
|
||||||
|
<string name="tibetan">Tybetański</string>
|
||||||
|
<string name="catalan">Kataloński</string>
|
||||||
|
<string name="czech">Czeski</string>
|
||||||
|
<string name="chamorro">Chamorro</string>
|
||||||
|
<string name="chechen">Czeczeński</string>
|
||||||
|
<string name="chuvash">Czuwasz</string>
|
||||||
|
<string name="cornish">Kornwalijski</string>
|
||||||
|
<string name="corsican">Korsykański</string>
|
||||||
|
<string name="german">Niemiecki</string>
|
||||||
|
<string name="dhivehi">Dhivehi</string>
|
||||||
|
<string name="danish_sign_language">Duński język migowy</string>
|
||||||
|
<string name="dzongkha">Dzongkha</string>
|
||||||
|
<string name="greek">Grecki</string>
|
||||||
|
<string name="english">Angielski</string>
|
||||||
|
<string name="estonian">Estoński</string>
|
||||||
|
<string name="basque">Baskijski</string>
|
||||||
|
<string name="ewe">Ewe</string>
|
||||||
|
<string name="faroese">Farerski</string>
|
||||||
|
<string name="persian">Perski</string>
|
||||||
|
<string name="fijian">Fidżi</string>
|
||||||
|
<string name="finnish">Fiński</string>
|
||||||
|
<string name="french">Francuski</string>
|
||||||
|
<string name="western_frisian">Zachodniofryzyjski</string>
|
||||||
|
<string name="scottish_gaelic">Szkocki Gaelic</string>
|
||||||
|
<string name="irish">Irlandzki</string>
|
||||||
|
<string name="manx">Manx</string>
|
||||||
|
<string name="guarani">Guarani</string>
|
||||||
|
<string name="german_sign_language">Niemiecki język migowy</string>
|
||||||
|
<string name="haitian">Haitian</string>
|
||||||
|
<string name="hausa">Hausa</string>
|
||||||
|
<string name="serbo_croatian">Sebsko-CHorwacki</string>
|
||||||
|
<string name="hebrew">Hebrajski</string>
|
||||||
|
<string name="hindi">Hindi</string>
|
||||||
|
<string name="hiri_motu">Hiri Motu</string>
|
||||||
|
<string name="croatian">Chorwacki</string>
|
||||||
|
<string name="igbo">Igbo</string>
|
||||||
|
<string name="sichuan_yi">Sichuan Yi</string>
|
||||||
|
<string name="inupiaq">Inupiaq</string>
|
||||||
|
<string name="icelandic">Islandzki</string>
|
||||||
|
<string name="italian">Włoski</string>
|
||||||
|
<string name="lojban">Lojban</string>
|
||||||
|
<string name="japanese">japoński</string>
|
||||||
|
<string name="japanese_sign_language">Japoński język migowy</string>
|
||||||
|
<string name="kabyle">Kabyle</string>
|
||||||
|
<string name="kalaallisut">Kalaallisut</string>
|
||||||
|
<string name="kannada">Kannada</string>
|
||||||
|
<string name="kashmiri">Kashmiri</string>
|
||||||
|
<string name="georgian">Gruziński</string>
|
||||||
|
<string name="kanuri">Kanuri</string>
|
||||||
|
<string name="kazakh">Kazakh</string>
|
||||||
|
<string name="kirghiz">Kirghiz</string>
|
||||||
|
<string name="komi">Komi</string>
|
||||||
|
<string name="kongo">Kongo</string>
|
||||||
|
<string name="kuanyama">Kuanyama</string>
|
||||||
|
<string name="lingala">Lingala</string>
|
||||||
|
<string name="latvian">Łotewski</string>
|
||||||
|
<string name="luxembourgish">Luksemburski</string>
|
||||||
|
<string name="luba_katanga">Luba-Katanga</string>
|
||||||
|
<string name="ganda">Ganda</string>
|
||||||
|
<string name="marshallese">Marshall</string>
|
||||||
|
<string name="malayalam">Malayalam</string>
|
||||||
|
<string name="marathi">Marathi</string>
|
||||||
|
<string name="macedonian">Macedoński</string>
|
||||||
|
<string name="maltese">Maltański</string>
|
||||||
|
<string name="south_ndebele">Południowe Ndebele</string>
|
||||||
|
<string name="north_ndebele">Północne Ndebele</string>
|
||||||
|
<string name="ndonga">Ndonga</string>
|
||||||
|
<string name="nepali_macrolanguage">Nepalski</string>
|
||||||
|
<string name="norwegian">Norweski</string>
|
||||||
|
<string name="romanian">Rumuński</string>
|
||||||
|
<string name="sango">Sango</string>
|
||||||
|
<string name="saudi_arabian_sign_language">Język migowy Arabii Saudyjskiej</string>
|
||||||
|
<string name="south_african_sign_language">Południowoafrykański język migowy</string>
|
||||||
|
<string name="northern_sami">Północni Lapończycy</string>
|
||||||
|
<string name="samoan">Samoan</string>
|
||||||
|
<string name="southern_sotho">Południowe Sotho</string>
|
||||||
|
<string name="spanish">Hiszpański</string>
|
||||||
|
<string name="albanian">Albański</string>
|
||||||
|
<string name="sardinian">Sardyński</string>
|
||||||
|
<string name="serbian">Serbski</string>
|
||||||
|
<string name="sundanese">Sundajski</string>
|
||||||
|
<string name="tahitian">Tahitański</string>
|
||||||
|
<string name="tamil">Tamilski</string>
|
||||||
|
<string name="tatar">Tatarski</string>
|
||||||
|
<string name="telugu">Telugu</string>
|
||||||
|
<string name="thai">Tajski</string>
|
||||||
|
<string name="tigrinya">Tigrinya</string>
|
||||||
|
<string name="tsonga">Tsonga</string>
|
||||||
|
<string name="turkmen">Turmeński</string>
|
||||||
|
<string name="yoruba">Yoruba</string>
|
||||||
|
<string name="zhuang">Zhuang</string>
|
||||||
|
<string name="chinese">Chiński</string>
|
||||||
|
<string name="bynd">Uznanie autorstwa - Bez utworów zależnych</string>
|
||||||
|
<string name="bync">Uznanie autorstwa - Użycie niekomercyjne</string>
|
||||||
|
<string name="byncnd">Uznanie autorstwa - Użycie niekomercyjne - Bez utworów zależnych</string>
|
||||||
|
<string name="public_domain">Domena Publiczna</string>
|
||||||
|
<string name="privacy_public">Publiczne</string>
|
||||||
|
<string name="privacy_private">Prywatne</string>
|
||||||
|
<string name="unlisted">Niepubliczne</string>
|
||||||
|
<string name="internal">Wewnętrzne</string>
|
||||||
|
<string name="username_error">Nazwa użytkownika nie może być pusta</string>
|
||||||
|
<string name="password_error">Hasło nie może być puste</string>
|
||||||
|
<string name="no_instance">Brak zarejestrowanego konta. Dodaj konto PeerTube używając \'+\' na górnym pasku</string>
|
||||||
|
<string name="permissions">Do transmisji na żywo potrzebny jest dostęp do kamery i mikrofonu. Kliknij poniżej, aby zmienić ustawienia uprawnień.</string>
|
||||||
|
<string name="delete_account">Usunąć konto %s powiązane z serwerem %s\?</string>
|
||||||
|
<string name="tags_rules">Maksymalnie 5 tagów, każdy o długości od 2 do 30 znaków, oddzielone przecinkiem</string>
|
||||||
|
<string name="cancel">Anuluj</string>
|
||||||
|
<string name="yes">Tak</string>
|
||||||
|
<string name="background_reason">Twoja transmisja na żywo zakończyła się, ponieważ aplikacja przeszła do pracy w tle</string>
|
||||||
|
<string name="lock_reason">Twoja transmisja na żywo zakończyła się, ponieważ telefon został zablokowany</string>
|
||||||
|
<string name="network_reason">Twoja transmisja na żywo zakończyła się z powodu problemów z siecią</string>
|
||||||
|
<string name="no">Nie</string>
|
||||||
|
<string name="comments_enabled">Włącz komentarze do filmu</string>
|
||||||
|
<string name="galician">Galicyjski</string>
|
||||||
|
<string name="gujarati">Gujarati</string>
|
||||||
|
<string name="exemple_instance">np. peertube.fr, https://peertube.fr</string>
|
||||||
|
<string name="hungarian">Węgierski</string>
|
||||||
|
<string name="akan">Akan</string>
|
||||||
|
<string name="bosnian">Bośniacki</string>
|
||||||
|
<string name="bulgarian">Bułgarski</string>
|
||||||
|
<string name="belarusian">Białoruski</string>
|
||||||
|
<string name="animals">Zwierzęta</string>
|
||||||
|
<string name="kids">Dzieci</string>
|
||||||
|
<string name="arabic">Arabski</string>
|
||||||
|
<string name="american_sign_language">Amerykański język migowy</string>
|
||||||
|
<string name="bislama">Bislama</string>
|
||||||
|
<string name="breton">Bretoński</string>
|
||||||
|
<string name="brazilian_sign_language">Brazylijski język migowy</string>
|
||||||
|
<string name="danish">Duński</string>
|
||||||
|
<string name="esperanto">Esperanto</string>
|
||||||
|
<string name="herero">Herero</string>
|
||||||
|
<string name="lithuanian">Litewski</string>
|
||||||
|
<string name="maori">Maori</string>
|
||||||
|
<string name="panjabi">Pendjabi</string>
|
||||||
|
<string name="polish">Polski</string>
|
||||||
|
<string name="portuguese">Portugalski</string>
|
||||||
|
<string name="rundi">Rundi</string>
|
||||||
|
<string name="cree">Cree</string>
|
||||||
|
<string name="czech_sign_language">Czeski język migowy</string>
|
||||||
|
<string name="chinese_sign_language">Chiński język migowy</string>
|
||||||
|
<string name="welsh">Walijski</string>
|
||||||
|
<string name="french_sign_language">Francuski język migowy</string>
|
||||||
|
<string name="fulah">Fulah</string>
|
||||||
|
<string name="armenian">Armeński</string>
|
||||||
|
<string name="inuktitut">Inuktitut</string>
|
||||||
|
<string name="indonesian">Indonesian</string>
|
||||||
|
<string name="javanese">Javanese</string>
|
||||||
|
<string name="limburgan">Limburgan</string>
|
||||||
|
<string name="malagasy">Malgaski</string>
|
||||||
|
<string name="norwegian_nynorsk">Norweski Nynorsk</string>
|
||||||
|
<string name="nyanja">Nyanja</string>
|
||||||
|
<string name="kikuyu">Kikuyu</string>
|
||||||
|
<string name="khmer">Khmer</string>
|
||||||
|
<string name="kinyarwanda">Rwanda</string>
|
||||||
|
<string name="korean">Koreański</string>
|
||||||
|
<string name="kurdish">Kurdyjski</string>
|
||||||
|
<string name="lao">Lao</string>
|
||||||
|
<string name="oriya_macrolanguage">Oriya</string>
|
||||||
|
<string name="malay_macrolanguage">Malajski</string>
|
||||||
|
<string name="nauru">Nauru</string>
|
||||||
|
<string name="navajo">Navaho</string>
|
||||||
|
<string name="mongolian">Mongolski</string>
|
||||||
|
<string name="burmese">Birmański</string>
|
||||||
|
<string name="dutch">Holenderski</string>
|
||||||
|
<string name="norwegian_bokmål">Norweski Bokmål</string>
|
||||||
|
<string name="occitan">Occitan</string>
|
||||||
|
<string name="oromo">Oromo</string>
|
||||||
|
<string name="ojibwa">Ojibwa</string>
|
||||||
|
<string name="ossetian">Ossetian</string>
|
||||||
|
<string name="pakistan_sign_language">Język migowy Pakistanu</string>
|
||||||
|
<string name="pushto">Pachto</string>
|
||||||
|
<string name="romansh">Romański</string>
|
||||||
|
<string name="russian_sign_language">Rosyjski język migowy</string>
|
||||||
|
<string name="russian">Rosyjski</string>
|
||||||
|
<string name="slovenian">Słoweński</string>
|
||||||
|
<string name="sindhi">Sindhi</string>
|
||||||
|
<string name="quechua">Quechua</string>
|
||||||
|
<string name="sinhala">Sinhala</string>
|
||||||
|
<string name="slovak">Słowacki</string>
|
||||||
|
<string name="shona">Shona</string>
|
||||||
|
<string name="somali">Somalijski</string>
|
||||||
|
<string name="swati">Swati</string>
|
||||||
|
<string name="tagalog">Tagalog</string>
|
||||||
|
<string name="swahili_macrolanguage">Swahili</string>
|
||||||
|
<string name="swedish">Szwedzki</string>
|
||||||
|
<string name="swedish_sign_language">Szwedzki język migowy</string>
|
||||||
|
<string name="tajik">Tajik</string>
|
||||||
|
<string name="klingon">Klingon</string>
|
||||||
|
<string name="tswana">Tswana</string>
|
||||||
|
<string name="xhosa">Xhosa</string>
|
||||||
|
<string name="yiddish">Yiddish</string>
|
||||||
|
<string name="bysa">Uznanie autorstwa - Na tych samych zasadach</string>
|
||||||
|
<string name="by">Uznanie autorstwa</string>
|
||||||
|
<string name="byncsa">Uznanie autorstwa - Użycie niekomercyjne - Na tych samych warunkach</string>
|
||||||
|
<string name="tonga_tonga_islands">Tonga</string>
|
||||||
|
<string name="twi">Twi</string>
|
||||||
|
<string name="ukrainian">Ukraiński</string>
|
||||||
|
<string name="walloon">Walloon</string>
|
||||||
|
<string name="turkish">Turecki</string>
|
||||||
|
<string name="uighur">Ujgurski</string>
|
||||||
|
<string name="urdu">Urdu</string>
|
||||||
|
<string name="uzbek">Uzbecki</string>
|
||||||
|
<string name="venda">Venda</string>
|
||||||
|
<string name="vietnamese">Wietnamski</string>
|
||||||
|
<string name="wolof">Wolof</string>
|
||||||
|
<string name="zulu">Zulu</string>
|
||||||
|
<string name="save_replay_info">Jeśli włączysz tę opcję, twoja transmisja zostanie przerwana jeśli przekroczysz limit miejsca na filmy</string>
|
||||||
|
</resources>
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">PeerTube Live</string>
|
|
||||||
<string name="json_error">Ошибка JSON</string>
|
|
||||||
<string name="unknwon_error">Неизвестная ошибка</string>
|
|
||||||
</resources>
|
|
12
build.gradle
12
build.gradle
|
@ -1,14 +1,16 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.5.10'
|
ext.kotlin_version = '1.6.21'
|
||||||
|
ext.rtsp_version_name = '2.1.9'
|
||||||
|
ext.rtsp_version_code = 219
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
|
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.4.2'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -18,7 +20,7 @@ buildscript {
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 32
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 32
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
@ -13,8 +15,11 @@ android {
|
||||||
consumerProguardFiles 'proguard-rules.pro'
|
consumerProguardFiles 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace 'com.pedro.encoder'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'androidx.annotation:annotation:1.2.0'
|
api 'androidx.annotation:annotation:1.3.0'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<manifest package="com.pedro.encoder" />
|
<manifest />
|
||||||
|
|
|
@ -1,37 +1,145 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder;
|
package com.pedro.encoder;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import com.pedro.encoder.utils.CodecUtil;
|
import com.pedro.encoder.utils.CodecUtil;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by pedro on 18/09/19.
|
* Created by pedro on 18/09/19.
|
||||||
*/
|
*/
|
||||||
public abstract class BaseEncoder implements EncoderCallback {
|
public abstract class BaseEncoder implements EncoderCallback {
|
||||||
|
|
||||||
private static final String TAG = "BaseEncoder";
|
protected String TAG = "BaseEncoder";
|
||||||
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
private final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
private HandlerThread handlerThread;
|
||||||
|
protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<>(80);
|
||||||
protected MediaCodec codec;
|
protected MediaCodec codec;
|
||||||
protected long presentTimeUs;
|
protected static long presentTimeUs;
|
||||||
protected volatile boolean running = false;
|
protected volatile boolean running = false;
|
||||||
protected boolean isBufferMode = true;
|
protected boolean isBufferMode = true;
|
||||||
protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND;
|
protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND;
|
||||||
|
private MediaCodec.Callback callback;
|
||||||
|
private long oldTimeStamp = 0L;
|
||||||
|
protected boolean shouldReset = true;
|
||||||
|
|
||||||
|
public void restart() {
|
||||||
|
start(false);
|
||||||
|
initCodec();
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
|
if (presentTimeUs == 0) {
|
||||||
|
presentTimeUs = System.nanoTime() / 1000;
|
||||||
|
}
|
||||||
start(true);
|
start(true);
|
||||||
|
initCodec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initCodec() {
|
||||||
|
handlerThread = new HandlerThread(TAG);
|
||||||
|
handlerThread.start();
|
||||||
|
Handler handler = new Handler(handlerThread.getLooper());
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
createAsyncCallback();
|
||||||
|
codec.setCallback(callback, handler);
|
||||||
|
codec.start();
|
||||||
|
} else {
|
||||||
|
codec.start();
|
||||||
|
handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
getDataFromEncoder();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.i(TAG, "Encoding error", e);
|
||||||
|
reloadCodec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void reset();
|
||||||
|
|
||||||
public abstract void start(boolean resetTs);
|
public abstract void start(boolean resetTs);
|
||||||
|
|
||||||
protected abstract void stopImp();
|
protected abstract void stopImp();
|
||||||
|
|
||||||
|
protected void fixTimeStamp(MediaCodec.BufferInfo info) {
|
||||||
|
if (oldTimeStamp > info.presentationTimeUs) {
|
||||||
|
info.presentationTimeUs = oldTimeStamp;
|
||||||
|
} else {
|
||||||
|
oldTimeStamp = info.presentationTimeUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadCodec() {
|
||||||
|
//Sometimes encoder crash, we will try recover it. Reset encoder a time if crash
|
||||||
|
if (shouldReset) {
|
||||||
|
Log.e(TAG, "Encoder crashed, trying to recover it");
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
stop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(boolean resetTs) {
|
||||||
|
if (resetTs) {
|
||||||
|
presentTimeUs = 0;
|
||||||
|
}
|
||||||
running = false;
|
running = false;
|
||||||
stopImp();
|
stopImp();
|
||||||
|
if (handlerThread != null) {
|
||||||
|
if (handlerThread.getLooper() != null) {
|
||||||
|
if (handlerThread.getLooper().getThread() != null) {
|
||||||
|
handlerThread.getLooper().getThread().interrupt();
|
||||||
|
}
|
||||||
|
handlerThread.getLooper().quit();
|
||||||
|
}
|
||||||
|
handlerThread.quit();
|
||||||
|
if (codec != null) {
|
||||||
|
try {
|
||||||
|
codec.flush();
|
||||||
|
} catch (IllegalStateException ignored) { }
|
||||||
|
}
|
||||||
|
//wait for thread to die for 500ms.
|
||||||
|
try {
|
||||||
|
handlerThread.getLooper().getThread().join(500);
|
||||||
|
} catch (Exception ignored) { }
|
||||||
|
}
|
||||||
|
queue.clear();
|
||||||
|
queue = new ArrayBlockingQueue<>(80);
|
||||||
try {
|
try {
|
||||||
codec.stop();
|
codec.stop();
|
||||||
codec.release();
|
codec.release();
|
||||||
|
@ -39,18 +147,19 @@ public abstract class BaseEncoder implements EncoderCallback {
|
||||||
} catch (IllegalStateException | NullPointerException e) {
|
} catch (IllegalStateException | NullPointerException e) {
|
||||||
codec = null;
|
codec = null;
|
||||||
}
|
}
|
||||||
|
oldTimeStamp = 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract MediaCodecInfo chooseEncoder(String mime);
|
protected abstract MediaCodecInfo chooseEncoder(String mime);
|
||||||
|
|
||||||
protected void getDataFromEncoder(Frame frame) throws IllegalStateException {
|
protected void getDataFromEncoder() throws IllegalStateException {
|
||||||
if (isBufferMode) {
|
if (isBufferMode) {
|
||||||
int inBufferIndex = codec.dequeueInputBuffer(0);
|
int inBufferIndex = codec.dequeueInputBuffer(0);
|
||||||
if (inBufferIndex >= 0) {
|
if (inBufferIndex >= 0) {
|
||||||
inputAvailable(codec, inBufferIndex, frame);
|
inputAvailable(codec, inBufferIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (; running; ) {
|
while (running) {
|
||||||
int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
|
int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
|
||||||
if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||||
MediaFormat mediaFormat = codec.getOutputFormat();
|
MediaFormat mediaFormat = codec.getOutputFormat();
|
||||||
|
@ -65,16 +174,22 @@ public abstract class BaseEncoder implements EncoderCallback {
|
||||||
|
|
||||||
protected abstract Frame getInputFrame() throws InterruptedException;
|
protected abstract Frame getInputFrame() throws InterruptedException;
|
||||||
|
|
||||||
|
protected abstract long calculatePts(Frame frame, long presentTimeUs);
|
||||||
|
|
||||||
private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
|
private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
|
||||||
int inBufferIndex, Frame frame) throws IllegalStateException {
|
int inBufferIndex) throws IllegalStateException {
|
||||||
try {
|
try {
|
||||||
if (frame == null) frame = getInputFrame();
|
Frame frame = getInputFrame();
|
||||||
|
while (frame == null) frame = getInputFrame();
|
||||||
byteBuffer.clear();
|
byteBuffer.clear();
|
||||||
byteBuffer.put(frame.getBuffer(), frame.getOffset(), frame.getSize());
|
int size = Math.max(0, Math.min(frame.getSize(), byteBuffer.remaining()) - frame.getOffset());
|
||||||
long pts = System.nanoTime() / 1000 - presentTimeUs;
|
byteBuffer.put(frame.getBuffer(), frame.getOffset(), size);
|
||||||
mediaCodec.queueInputBuffer(inBufferIndex, 0, frame.getSize(), pts, 0);
|
long pts = calculatePts(frame, presentTimeUs);
|
||||||
|
mediaCodec.queueInputBuffer(inBufferIndex, 0, size, pts, 0);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (NullPointerException | IndexOutOfBoundsException e) {
|
||||||
|
Log.i(TAG, "Encoding error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +215,7 @@ public abstract class BaseEncoder implements EncoderCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
|
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
|
||||||
throws IllegalStateException {
|
throws IllegalStateException {
|
||||||
ByteBuffer byteBuffer;
|
ByteBuffer byteBuffer;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
@ -108,7 +223,7 @@ public abstract class BaseEncoder implements EncoderCallback {
|
||||||
} else {
|
} else {
|
||||||
byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
|
byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
|
||||||
}
|
}
|
||||||
processInput(byteBuffer, mediaCodec, inBufferIndex, frame);
|
processInput(byteBuffer, mediaCodec, inBufferIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -122,4 +237,41 @@ public abstract class BaseEncoder implements EncoderCallback {
|
||||||
}
|
}
|
||||||
processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo);
|
processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
|
private void createAsyncCallback() {
|
||||||
|
callback = new MediaCodec.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex) {
|
||||||
|
try {
|
||||||
|
inputAvailable(mediaCodec, inBufferIndex);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.i(TAG, "Encoding error", e);
|
||||||
|
reloadCodec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
|
||||||
|
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
try {
|
||||||
|
outputAvailable(mediaCodec, outBufferIndex, bufferInfo);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.i(TAG, "Encoding error", e);
|
||||||
|
reloadCodec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
|
||||||
|
Log.e(TAG, "Error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec,
|
||||||
|
@NonNull MediaFormat mediaFormat) {
|
||||||
|
formatChanged(mediaCodec, mediaFormat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder;
|
package com.pedro.encoder;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
@ -8,7 +24,7 @@ import androidx.annotation.NonNull;
|
||||||
* Created by pedro on 18/09/19.
|
* Created by pedro on 18/09/19.
|
||||||
*/
|
*/
|
||||||
public interface EncoderCallback {
|
public interface EncoderCallback {
|
||||||
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
|
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
|
||||||
throws IllegalStateException;
|
throws IllegalStateException;
|
||||||
|
|
||||||
void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
|
void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder;
|
package com.pedro.encoder;
|
||||||
|
|
||||||
import android.graphics.ImageFormat;
|
import android.graphics.ImageFormat;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.audio;
|
package com.pedro.encoder.audio;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
@ -7,11 +23,10 @@ import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import com.pedro.encoder.BaseEncoder;
|
import com.pedro.encoder.BaseEncoder;
|
||||||
import com.pedro.encoder.Frame;
|
import com.pedro.encoder.Frame;
|
||||||
|
import com.pedro.encoder.GetFrame;
|
||||||
import com.pedro.encoder.input.audio.GetMicrophoneData;
|
import com.pedro.encoder.input.audio.GetMicrophoneData;
|
||||||
import com.pedro.encoder.utils.CodecUtil;
|
import com.pedro.encoder.utils.CodecUtil;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,15 +37,18 @@ import java.util.List;
|
||||||
|
|
||||||
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
||||||
|
|
||||||
private static final String TAG = "AudioEncoder";
|
private final GetAacData getAacData;
|
||||||
|
|
||||||
private GetAacData getAacData;
|
|
||||||
private int bitRate = 64 * 1024; //in kbps
|
private int bitRate = 64 * 1024; //in kbps
|
||||||
private int sampleRate = 32000; //in hz
|
private int sampleRate = 32000; //in hz
|
||||||
|
private int maxInputSize = 0;
|
||||||
private boolean isStereo = true;
|
private boolean isStereo = true;
|
||||||
|
private GetFrame getFrame;
|
||||||
|
private long bytesRead = 0;
|
||||||
|
private boolean tsModeBuffer = false;
|
||||||
|
|
||||||
public AudioEncoder(GetAacData getAacData) {
|
public AudioEncoder(GetAacData getAacData) {
|
||||||
this.getAacData = getAacData;
|
this.getAacData = getAacData;
|
||||||
|
TAG = "AudioEncoder";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,31 +56,19 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
||||||
*/
|
*/
|
||||||
public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
|
public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
|
||||||
int maxInputSize) {
|
int maxInputSize) {
|
||||||
|
this.bitRate = bitRate;
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
|
this.maxInputSize = maxInputSize;
|
||||||
|
this.isStereo = isStereo;
|
||||||
isBufferMode = true;
|
isBufferMode = true;
|
||||||
try {
|
try {
|
||||||
List<MediaCodecInfo> encoders = new ArrayList<>();
|
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
|
||||||
if (force == CodecUtil.Force.HARDWARE) {
|
if (encoder != null) {
|
||||||
encoders = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
|
Log.i(TAG, "Encoder selected " + encoder.getName());
|
||||||
} else if (force == CodecUtil.Force.SOFTWARE) {
|
codec = MediaCodec.createByCodecName(encoder.getName());
|
||||||
encoders = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force == CodecUtil.Force.FIRST_COMPATIBLE_FOUND) {
|
|
||||||
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
|
|
||||||
if (encoder != null) {
|
|
||||||
codec = MediaCodec.createByCodecName(encoder.getName());
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Valid encoder not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (encoders.isEmpty()) {
|
Log.e(TAG, "Valid encoder not found");
|
||||||
Log.e(TAG, "Valid encoder not found");
|
return false;
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
codec = MediaCodec.createByCodecName(encoders.get(0).getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int channelCount = (isStereo) ? 2 : 1;
|
int channelCount = (isStereo) ? 2 : 1;
|
||||||
|
@ -76,41 +82,65 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
||||||
running = false;
|
running = false;
|
||||||
Log.i(TAG, "prepared");
|
Log.i(TAG, "prepared");
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException | IllegalStateException e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Create AudioEncoder failed.", e);
|
Log.e(TAG, "Create AudioEncoder failed.", e);
|
||||||
|
this.stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGetFrame(GetFrame getFrame) {
|
||||||
|
this.getFrame = getFrame;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare encoder with default parameters
|
* Prepare encoder with default parameters
|
||||||
*/
|
*/
|
||||||
public boolean prepareAudioEncoder() {
|
public boolean prepareAudioEncoder() {
|
||||||
return prepareAudioEncoder(bitRate, sampleRate, isStereo, 0);
|
return prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(boolean resetTs) {
|
public void start(boolean resetTs) {
|
||||||
presentTimeUs = System.nanoTime() / 1000;
|
shouldReset = resetTs;
|
||||||
codec.start();
|
|
||||||
running = true;
|
|
||||||
Log.i(TAG, "started");
|
Log.i(TAG, "started");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void stopImp() {
|
protected void stopImp() {
|
||||||
|
bytesRead = 0;
|
||||||
Log.i(TAG, "stopped");
|
Log.i(TAG, "stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
stop(false);
|
||||||
|
prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Frame getInputFrame() throws InterruptedException {
|
protected Frame getInputFrame() throws InterruptedException {
|
||||||
return null;
|
return getFrame != null ? getFrame.getInputFrame() : queue.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long calculatePts(Frame frame, long presentTimeUs) {
|
||||||
|
long pts;
|
||||||
|
if (tsModeBuffer) {
|
||||||
|
int channels = isStereo ? 2 : 1;
|
||||||
|
pts = 1000000 * bytesRead / 2 / channels / sampleRate;
|
||||||
|
bytesRead += frame.getSize();
|
||||||
|
} else {
|
||||||
|
pts = System.nanoTime() / 1000 - presentTimeUs;
|
||||||
|
}
|
||||||
|
return pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
|
protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
|
||||||
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
fixTimeStamp(bufferInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,39 +153,45 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
||||||
* Set custom PCM data.
|
* Set custom PCM data.
|
||||||
* Use it after prepareAudioEncoder(int sampleRate, int channel).
|
* Use it after prepareAudioEncoder(int sampleRate, int channel).
|
||||||
* Used too with microphone.
|
* Used too with microphone.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void inputPCMData(Frame frame) {
|
public void inputPCMData(Frame frame) {
|
||||||
if (running) {
|
if (running && !queue.offer(frame)) {
|
||||||
try {
|
|
||||||
getDataFromEncoder(frame);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.i(TAG, "Encoding error", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "frame discarded");
|
Log.i(TAG, "frame discarded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MediaCodecInfo chooseEncoder(String mime) {
|
protected MediaCodecInfo chooseEncoder(String mime) {
|
||||||
List<MediaCodecInfo> mediaCodecInfoList = CodecUtil.getAllEncoders(mime);
|
List<MediaCodecInfo> mediaCodecInfoList;
|
||||||
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
|
if (force == CodecUtil.Force.HARDWARE) {
|
||||||
String name = mediaCodecInfo.getName().toLowerCase();
|
mediaCodecInfoList = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
|
||||||
if (!name.contains("omx.google")) return mediaCodecInfo;
|
} else if (force == CodecUtil.Force.SOFTWARE) {
|
||||||
}
|
mediaCodecInfoList = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
|
||||||
if (mediaCodecInfoList.size() > 0) {
|
|
||||||
return mediaCodecInfoList.get(0);
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
//Priority: hardware > software
|
||||||
|
mediaCodecInfoList = CodecUtil.getAllEncoders(CodecUtil.AAC_MIME, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, mediaCodecInfoList.size() + " encoders found");
|
||||||
|
if (mediaCodecInfoList.isEmpty()) return null;
|
||||||
|
else return mediaCodecInfoList.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSampleRate(int sampleRate) {
|
public void setSampleRate(int sampleRate) {
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTsModeBuffer() {
|
||||||
|
return tsModeBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTsModeBuffer(boolean tsModeBuffer) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
this.tsModeBuffer = tsModeBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
|
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
|
||||||
getAacData.onAudioFormat(mediaFormat);
|
getAacData.onAudioFormat(mediaFormat);
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.audio;
|
package com.pedro.encoder.audio;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.audio;
|
package com.pedro.encoder.input.audio;
|
||||||
|
|
||||||
import android.media.audiofx.AcousticEchoCanceler;
|
import android.media.audiofx.AcousticEchoCanceler;
|
||||||
|
@ -13,7 +29,7 @@ public class AudioPostProcessEffect {
|
||||||
|
|
||||||
private final String TAG = "AudioPostProcessEffect";
|
private final String TAG = "AudioPostProcessEffect";
|
||||||
|
|
||||||
private int microphoneId;
|
private final int microphoneId;
|
||||||
private AcousticEchoCanceler acousticEchoCanceler = null;
|
private AcousticEchoCanceler acousticEchoCanceler = null;
|
||||||
private AutomaticGainControl automaticGainControl = null;
|
private AutomaticGainControl automaticGainControl = null;
|
||||||
private NoiseSuppressor noiseSuppressor = null;
|
private NoiseSuppressor noiseSuppressor = null;
|
||||||
|
@ -25,11 +41,13 @@ public class AudioPostProcessEffect {
|
||||||
public void enableAutoGainControl() {
|
public void enableAutoGainControl() {
|
||||||
if (AutomaticGainControl.isAvailable() && automaticGainControl == null) {
|
if (AutomaticGainControl.isAvailable() && automaticGainControl == null) {
|
||||||
automaticGainControl = AutomaticGainControl.create(microphoneId);
|
automaticGainControl = AutomaticGainControl.create(microphoneId);
|
||||||
automaticGainControl.setEnabled(true);
|
if (automaticGainControl != null) {
|
||||||
Log.i(TAG, "AutoGainControl enabled");
|
automaticGainControl.setEnabled(true);
|
||||||
} else {
|
Log.i(TAG, "AutoGainControl enabled");
|
||||||
Log.e(TAG, "This device don't support AutoGainControl");
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Log.e(TAG, "This device doesn't implement AutoGainControl");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseAutoGainControl() {
|
public void releaseAutoGainControl() {
|
||||||
|
@ -43,11 +61,13 @@ public class AudioPostProcessEffect {
|
||||||
public void enableEchoCanceler() {
|
public void enableEchoCanceler() {
|
||||||
if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) {
|
if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) {
|
||||||
acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId);
|
acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId);
|
||||||
acousticEchoCanceler.setEnabled(true);
|
if (acousticEchoCanceler != null) {
|
||||||
Log.i(TAG, "EchoCanceler enabled");
|
acousticEchoCanceler.setEnabled(true);
|
||||||
} else {
|
Log.i(TAG, "EchoCanceler enabled");
|
||||||
Log.e(TAG, "This device don't support EchoCanceler");
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Log.e(TAG, "This device doesn't implement EchoCanceler");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseEchoCanceler() {
|
public void releaseEchoCanceler() {
|
||||||
|
@ -61,11 +81,13 @@ public class AudioPostProcessEffect {
|
||||||
public void enableNoiseSuppressor() {
|
public void enableNoiseSuppressor() {
|
||||||
if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) {
|
if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) {
|
||||||
noiseSuppressor = NoiseSuppressor.create(microphoneId);
|
noiseSuppressor = NoiseSuppressor.create(microphoneId);
|
||||||
noiseSuppressor.setEnabled(true);
|
if (noiseSuppressor != null) {
|
||||||
Log.i(TAG, "NoiseSuppressor enabled");
|
noiseSuppressor.setEnabled(true);
|
||||||
} else {
|
Log.i(TAG, "NoiseSuppressor enabled");
|
||||||
Log.e(TAG, "This device don't support NoiseSuppressor");
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Log.e(TAG, "This device doesn't implement NoiseSuppressor");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseNoiseSuppressor() {
|
public void releaseNoiseSuppressor() {
|
||||||
|
@ -75,4 +97,10 @@ public class AudioPostProcessEffect {
|
||||||
noiseSuppressor = null;
|
noiseSuppressor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
releaseAutoGainControl();
|
||||||
|
releaseEchoCanceler();
|
||||||
|
releaseNoiseSuppressor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.audio;
|
package com.pedro.encoder.input.audio;
|
||||||
|
|
||||||
public abstract class CustomAudioEffect {
|
public abstract class CustomAudioEffect {
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.audio;
|
package com.pedro.encoder.input.audio;
|
||||||
|
|
||||||
import com.pedro.encoder.Frame;
|
import com.pedro.encoder.Frame;
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.audio;
|
package com.pedro.encoder.input.audio;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioPlaybackCaptureConfiguration;
|
import android.media.AudioPlaybackCaptureConfiguration;
|
||||||
import android.media.AudioRecord;
|
import android.media.AudioRecord;
|
||||||
|
@ -16,28 +33,29 @@ import java.nio.ByteBuffer;
|
||||||
* Created by pedro on 19/01/17.
|
* Created by pedro on 19/01/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
public class MicrophoneManager {
|
public class MicrophoneManager {
|
||||||
|
|
||||||
private final String TAG = "MicrophoneManager";
|
private final String TAG = "MicrophoneManager";
|
||||||
private static final int BUFFER_SIZE = 4096;
|
private int BUFFER_SIZE = 0;
|
||||||
protected AudioRecord audioRecord;
|
protected AudioRecord audioRecord;
|
||||||
private GetMicrophoneData getMicrophoneData;
|
private final GetMicrophoneData getMicrophoneData;
|
||||||
private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
protected byte[] pcmBuffer = new byte[BUFFER_SIZE];
|
||||||
private byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
|
protected byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
|
||||||
protected boolean running = false;
|
protected boolean running = false;
|
||||||
private boolean created = false;
|
private boolean created = false;
|
||||||
|
|
||||||
//default parameters for microphone
|
//default parameters for microphone
|
||||||
private int sampleRate = 32000; //hz
|
private int sampleRate = 32000; //hz
|
||||||
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
private int channel = AudioFormat.CHANNEL_IN_STEREO;
|
private int channel = AudioFormat.CHANNEL_IN_STEREO;
|
||||||
private boolean muted = false;
|
protected boolean muted = false;
|
||||||
private AudioPostProcessEffect audioPostProcessEffect;
|
private AudioPostProcessEffect audioPostProcessEffect;
|
||||||
HandlerThread handlerThread;
|
protected HandlerThread handlerThread;
|
||||||
private CustomAudioEffect customAudioEffect = new NoAudioEffect();
|
protected CustomAudioEffect customAudioEffect = new NoAudioEffect();
|
||||||
|
|
||||||
public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
|
public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
|
||||||
this.getMicrophoneData = getMicrophoneData;
|
this.getMicrophoneData = getMicrophoneData;
|
||||||
|
getPcmBufferSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) {
|
public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) {
|
||||||
|
@ -55,61 +73,86 @@ public class MicrophoneManager {
|
||||||
/**
|
/**
|
||||||
* Create audio record with params and default audio source
|
* Create audio record with params and default audio source
|
||||||
*/
|
*/
|
||||||
public void createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
|
public boolean createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
|
||||||
boolean noiseSuppressor) {
|
boolean noiseSuppressor) {
|
||||||
createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler, noiseSuppressor);
|
return createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler,
|
||||||
|
noiseSuppressor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create audio record with params and selected audio source
|
* Create audio record with params and selected audio source
|
||||||
* @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the recording source definitions.
|
*
|
||||||
|
* @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the
|
||||||
|
* recording source definitions.
|
||||||
*/
|
*/
|
||||||
public void createMicrophone(int audioSource, int sampleRate, boolean isStereo, boolean echoCanceler,
|
public boolean createMicrophone(int audioSource, int sampleRate, boolean isStereo,
|
||||||
boolean noiseSuppressor) {
|
boolean echoCanceler, boolean noiseSuppressor) {
|
||||||
this.sampleRate = sampleRate;
|
try {
|
||||||
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO;
|
this.sampleRate = sampleRate;
|
||||||
audioRecord =
|
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
|
||||||
new AudioRecord(audioSource, sampleRate, channel, audioFormat,
|
audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, getMaxInputSize() * 5);
|
||||||
getPcmBufferSize());
|
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
|
||||||
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
|
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
|
||||||
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
|
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
|
||||||
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
|
String chl = (isStereo) ? "Stereo" : "Mono";
|
||||||
String chl = (isStereo) ? "Stereo" : "Mono";
|
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||||
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
|
throw new IllegalArgumentException("Some parameters specified is not valid");
|
||||||
created = true;
|
}
|
||||||
|
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
|
||||||
|
created = true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.e(TAG, "create microphone error", e);
|
||||||
|
}
|
||||||
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal audio
|
* Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal
|
||||||
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling this!
|
* audio
|
||||||
*
|
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling
|
||||||
* @param config - AudioPlaybackCaptureConfiguration received from {@link android.media.projection.MediaProjection}
|
* this!
|
||||||
*
|
*
|
||||||
|
* @param config - AudioPlaybackCaptureConfiguration received from {@link
|
||||||
|
* android.media.projection.MediaProjection}
|
||||||
* @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection)
|
* @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection)
|
||||||
* @see "https://developer.android.com/guide/topics/media/playback-capture"
|
* @see "https://developer.android.com/guide/topics/media/playback-capture"
|
||||||
* @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9"
|
* @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9"
|
||||||
*/
|
*/
|
||||||
public void createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate, boolean isStereo) {
|
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) {
|
||||||
this.sampleRate = sampleRate;
|
try {
|
||||||
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO;
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
audioRecord = new AudioRecord.Builder()
|
this.sampleRate = sampleRate;
|
||||||
.setAudioPlaybackCaptureConfig(config)
|
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
|
||||||
.setAudioFormat(new AudioFormat.Builder()
|
audioRecord = new AudioRecord.Builder().setAudioPlaybackCaptureConfig(config)
|
||||||
.setEncoding(audioFormat)
|
.setAudioFormat(new AudioFormat.Builder().setEncoding(audioFormat)
|
||||||
.setSampleRate(sampleRate)
|
.setSampleRate(sampleRate)
|
||||||
.setChannelMask(channel)
|
.setChannelMask(channel)
|
||||||
.build())
|
.build())
|
||||||
.setBufferSizeInBytes(getPcmBufferSize())
|
.setBufferSizeInBytes(getMaxInputSize() * 5)
|
||||||
.build();
|
.build();
|
||||||
|
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
|
||||||
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
|
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
|
||||||
String chl = (isStereo) ? "Stereo" : "Mono";
|
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
|
||||||
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
|
String chl = (isStereo) ? "Stereo" : "Mono";
|
||||||
created = true;
|
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||||
} else createMicrophone(sampleRate, isStereo, false, false);
|
throw new IllegalArgumentException("Some parameters specified is not valid");
|
||||||
|
}
|
||||||
|
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
|
||||||
|
created = true;
|
||||||
|
} else {
|
||||||
|
return createMicrophone(sampleRate, isStereo, echoCanceler, noiseSuppressor);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.e(TAG, "create microphone error", e);
|
||||||
|
}
|
||||||
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
|
||||||
|
boolean isStereo) {
|
||||||
|
return createInternalMicrophone(config, sampleRate, isStereo, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start record and get data
|
* Start record and get data
|
||||||
|
@ -126,8 +169,6 @@ public class MicrophoneManager {
|
||||||
Frame frame = read();
|
Frame frame = read();
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
getMicrophoneData.inputPCMData(frame);
|
getMicrophoneData.inputPCMData(frame);
|
||||||
} else {
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,14 +201,10 @@ public class MicrophoneManager {
|
||||||
/**
|
/**
|
||||||
* @return Object with size and PCM buffer data
|
* @return Object with size and PCM buffer data
|
||||||
*/
|
*/
|
||||||
private Frame read() {
|
protected Frame read() {
|
||||||
pcmBuffer.rewind();
|
int size = audioRecord.read(pcmBuffer, 0, pcmBuffer.length);
|
||||||
int size = audioRecord.read(pcmBuffer, pcmBuffer.remaining());
|
if (size < 0) return null;
|
||||||
if (size <= 0) {
|
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer), 0, size);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer.array()),
|
|
||||||
muted ? 0 : pcmBuffer.arrayOffset(), size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,8 +227,7 @@ public class MicrophoneManager {
|
||||||
audioRecord = null;
|
audioRecord = null;
|
||||||
}
|
}
|
||||||
if (audioPostProcessEffect != null) {
|
if (audioPostProcessEffect != null) {
|
||||||
audioPostProcessEffect.releaseEchoCanceler();
|
audioPostProcessEffect.release();
|
||||||
audioPostProcessEffect.releaseNoiseSuppressor();
|
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Microphone stopped");
|
Log.i(TAG, "Microphone stopped");
|
||||||
}
|
}
|
||||||
|
@ -199,10 +235,10 @@ public class MicrophoneManager {
|
||||||
/**
|
/**
|
||||||
* Get PCM buffer size
|
* Get PCM buffer size
|
||||||
*/
|
*/
|
||||||
private int getPcmBufferSize() {
|
private void getPcmBufferSize() {
|
||||||
int pcmBufSize =
|
BUFFER_SIZE = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
|
||||||
AudioRecord.getMinBufferSize(sampleRate, channel, AudioFormat.ENCODING_PCM_16BIT);
|
pcmBuffer = new byte[BUFFER_SIZE];
|
||||||
return pcmBufSize * 5;
|
pcmBufferMuted = new byte[BUFFER_SIZE];
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxInputSize() {
|
public int getMaxInputSize() {
|
||||||
|
|
|
@ -1,15 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.audio;
|
package com.pedro.encoder.input.audio;
|
||||||
|
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.pedro.encoder.Frame;
|
||||||
|
import com.pedro.encoder.GetFrame;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to MicrophoneManager but samples are not read automatically.
|
* Similar to MicrophoneManager but samples are not read automatically.
|
||||||
* The owner must manually call read(...) as often as samples are needed.
|
* The owner must manually call read(...) as often as samples are needed.
|
||||||
*/
|
*/
|
||||||
public class MicrophoneManagerManual extends MicrophoneManager {
|
public class MicrophoneManagerManual extends MicrophoneManager implements GetFrame {
|
||||||
|
|
||||||
private final String TAG = "MicMM";
|
private final String TAG = "MicMM";
|
||||||
|
|
||||||
|
@ -54,4 +72,13 @@ public class MicrophoneManagerManual extends MicrophoneManager {
|
||||||
handlerThread = new HandlerThread("nothing");
|
handlerThread = new HandlerThread("nothing");
|
||||||
super.stop();
|
super.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetFrame getGetFrame() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Frame getInputFrame() {
|
||||||
|
return read();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.audio;
|
package com.pedro.encoder.input.audio;
|
||||||
|
|
||||||
public class NoAudioEffect extends CustomAudioEffect {
|
public class NoAudioEffect extends CustomAudioEffect {
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.decoder;
|
package com.pedro.encoder.input.decoder;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
@ -6,160 +22,110 @@ import android.media.MediaFormat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.pedro.encoder.Frame;
|
import com.pedro.encoder.Frame;
|
||||||
import com.pedro.encoder.input.audio.GetMicrophoneData;
|
import com.pedro.encoder.input.audio.GetMicrophoneData;
|
||||||
|
import com.pedro.encoder.utils.CodecUtil;
|
||||||
import com.pedro.encoder.utils.PCMUtil;
|
import com.pedro.encoder.utils.PCMUtil;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by pedro on 20/06/17.
|
* Created by pedro on 20/06/17.
|
||||||
*/
|
*/
|
||||||
public class AudioDecoder {
|
public class AudioDecoder extends BaseDecoder {
|
||||||
|
|
||||||
private final String TAG = "AudioDecoder";
|
|
||||||
|
|
||||||
private AudioDecoderInterface audioDecoderInterface;
|
private AudioDecoderInterface audioDecoderInterface;
|
||||||
private LoopFileInterface loopFileInterface;
|
|
||||||
private MediaExtractor audioExtractor;
|
|
||||||
private MediaCodec audioDecoder;
|
|
||||||
private MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();
|
|
||||||
private boolean decoding;
|
|
||||||
private Thread thread;
|
|
||||||
private GetMicrophoneData getMicrophoneData;
|
private GetMicrophoneData getMicrophoneData;
|
||||||
private MediaFormat audioFormat;
|
|
||||||
private String mime = "";
|
|
||||||
private int sampleRate;
|
private int sampleRate;
|
||||||
private boolean isStereo;
|
private boolean isStereo;
|
||||||
private int channels = 1;
|
private int channels = 1;
|
||||||
private int size = 2048;
|
private int size = 2048;
|
||||||
private byte[] pcmBuffer = new byte[size];
|
private byte[] pcmBuffer = new byte[size];
|
||||||
private byte[] pcmBufferMuted = new byte[11];
|
private byte[] pcmBufferMuted = new byte[11];
|
||||||
private static boolean loopMode = false;
|
|
||||||
private boolean muted = false;
|
private boolean muted = false;
|
||||||
private long duration;
|
|
||||||
private volatile long seekTime = 0;
|
|
||||||
private volatile long startMs = 0;
|
|
||||||
|
|
||||||
public AudioDecoder(GetMicrophoneData getMicrophoneData,
|
public AudioDecoder(GetMicrophoneData getMicrophoneData,
|
||||||
AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) {
|
AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) {
|
||||||
|
super(loopFileInterface);
|
||||||
|
TAG = "AudioDecoder";
|
||||||
this.getMicrophoneData = getMicrophoneData;
|
this.getMicrophoneData = getMicrophoneData;
|
||||||
this.audioDecoderInterface = audioDecoderInterface;
|
this.audioDecoderInterface = audioDecoderInterface;
|
||||||
this.loopFileInterface = loopFileInterface;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean initExtractor(String filePath) throws IOException {
|
@Override
|
||||||
decoding = false;
|
protected boolean extract(MediaExtractor audioExtractor) {
|
||||||
audioExtractor = new MediaExtractor();
|
size = 2048;
|
||||||
audioExtractor.setDataSource(filePath);
|
running = false;
|
||||||
for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) {
|
for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) {
|
||||||
audioFormat = audioExtractor.getTrackFormat(i);
|
mediaFormat = audioExtractor.getTrackFormat(i);
|
||||||
mime = audioFormat.getString(MediaFormat.KEY_MIME);
|
mime = mediaFormat.getString(MediaFormat.KEY_MIME);
|
||||||
if (mime.startsWith("audio/")) {
|
if (mime.startsWith("audio/")) {
|
||||||
audioExtractor.selectTrack(i);
|
audioExtractor.selectTrack(i);
|
||||||
} else {
|
} else {
|
||||||
audioFormat = null;
|
mediaFormat = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (audioFormat != null) {
|
if (mediaFormat != null) {
|
||||||
channels = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
|
channels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
|
||||||
isStereo = channels >= 2;
|
isStereo = channels >= 2;
|
||||||
sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
|
sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
|
||||||
duration = audioFormat.getLong(MediaFormat.KEY_DURATION);
|
duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
|
||||||
if (channels >= 2) {
|
fixBuffer();
|
||||||
pcmBuffer = new byte[2048 * channels];
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
//audio decoder not supported
|
//audio decoder not supported
|
||||||
} else {
|
} else {
|
||||||
mime = "";
|
mime = "";
|
||||||
audioFormat = null;
|
mediaFormat = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fixBuffer() {
|
||||||
|
if (channels >= 2) {
|
||||||
|
size *= channels;
|
||||||
|
}
|
||||||
|
pcmBuffer = new byte[size];
|
||||||
|
}
|
||||||
|
|
||||||
public boolean prepareAudio() {
|
public boolean prepareAudio() {
|
||||||
try {
|
return prepare(null);
|
||||||
audioDecoder = MediaCodec.createDecoderByType(mime);
|
|
||||||
audioDecoder.configure(audioFormat, null, null, 0);
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Prepare decoder error:", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void reset() {
|
||||||
decoding = true;
|
resetCodec(null);
|
||||||
audioDecoder.start();
|
|
||||||
thread = new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
decodeAudio();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.i(TAG, "Decoding error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
@Override
|
||||||
decoding = false;
|
protected void decode() {
|
||||||
seekTime = 0;
|
ByteBuffer[] inputBuffers = codec.getInputBuffers();
|
||||||
if (thread != null) {
|
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
|
||||||
thread.interrupt();
|
|
||||||
try {
|
|
||||||
thread.join(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
thread.interrupt();
|
|
||||||
}
|
|
||||||
thread = null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
audioDecoder.stop();
|
|
||||||
audioDecoder.release();
|
|
||||||
audioDecoder = null;
|
|
||||||
} catch (IllegalStateException | NullPointerException e) {
|
|
||||||
audioDecoder = null;
|
|
||||||
}
|
|
||||||
if (audioExtractor != null) {
|
|
||||||
audioExtractor.release();
|
|
||||||
audioExtractor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decodeAudio() throws IllegalStateException {
|
|
||||||
ByteBuffer[] inputBuffers = audioDecoder.getInputBuffers();
|
|
||||||
ByteBuffer[] outputBuffers = audioDecoder.getOutputBuffers();
|
|
||||||
startMs = System.currentTimeMillis();
|
startMs = System.currentTimeMillis();
|
||||||
while (decoding) {
|
while (running) {
|
||||||
int inIndex = audioDecoder.dequeueInputBuffer(10000);
|
int inIndex = codec.dequeueInputBuffer(10000);
|
||||||
if (inIndex >= 0) {
|
if (inIndex >= 0) {
|
||||||
ByteBuffer buffer = inputBuffers[inIndex];
|
ByteBuffer buffer = inputBuffers[inIndex];
|
||||||
int sampleSize = audioExtractor.readSampleData(buffer, 0);
|
int sampleSize = extractor.readSampleData(buffer, 0);
|
||||||
if (sampleSize < 0) {
|
if (sampleSize < 0) {
|
||||||
audioDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||||
} else {
|
} else {
|
||||||
audioDecoder.queueInputBuffer(inIndex, 0, sampleSize, audioExtractor.getSampleTime(), 0);
|
codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
|
||||||
audioExtractor.advance();
|
extractor.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
int outIndex = audioDecoder.dequeueOutputBuffer(audioInfo, 10000);
|
int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
|
||||||
switch (outIndex) {
|
switch (outIndex) {
|
||||||
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
||||||
outputBuffers = audioDecoder.getOutputBuffers();
|
outputBuffers = codec.getOutputBuffers();
|
||||||
break;
|
break;
|
||||||
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
||||||
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//needed for fix decode speed
|
long extractorTs = extractor.getSampleTime() / 1000;
|
||||||
while (audioExtractor.getSampleTime() / 1000
|
long currentTs = System.currentTimeMillis() - startMs + seekTime;
|
||||||
> System.currentTimeMillis() - startMs + seekTime) {
|
if (extractorTs > currentTs) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(10);
|
long sleepTime = extractorTs - currentTs;
|
||||||
|
Thread.sleep(sleepTime);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
if (thread != null) thread.interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,13 +133,13 @@ public class AudioDecoder {
|
||||||
//This buffer is PCM data
|
//This buffer is PCM data
|
||||||
if (muted) {
|
if (muted) {
|
||||||
outBuffer.get(pcmBufferMuted, 0,
|
outBuffer.get(pcmBufferMuted, 0,
|
||||||
outBuffer.remaining() <= pcmBufferMuted.length ? outBuffer.remaining()
|
Math.min(outBuffer.remaining(), pcmBufferMuted.length));
|
||||||
: pcmBufferMuted.length);
|
|
||||||
getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length));
|
getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length));
|
||||||
} else {
|
} else {
|
||||||
outBuffer.get(pcmBuffer, 0,
|
if (pcmBuffer.length < outBuffer.remaining()) {
|
||||||
outBuffer.remaining() <= pcmBuffer.length ? outBuffer.remaining()
|
pcmBuffer = new byte[outBuffer.remaining()];
|
||||||
: pcmBuffer.length);
|
}
|
||||||
|
outBuffer.get(pcmBuffer, 0, Math.min(outBuffer.remaining(), pcmBuffer.length));
|
||||||
if (channels > 2) { //downgrade to stereo
|
if (channels > 2) { //downgrade to stereo
|
||||||
byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels);
|
byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels);
|
||||||
getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length));
|
getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length));
|
||||||
|
@ -181,42 +147,54 @@ public class AudioDecoder {
|
||||||
getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length));
|
getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audioDecoder.releaseOutputBuffer(outIndex, false);
|
codec.releaseOutputBuffer(outIndex, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All decoded frames have been rendered, we can stop playing now
|
// All decoded frames have been rendered, we can stop playing now
|
||||||
if ((audioInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||||
seekTime = 0;
|
seekTime = 0;
|
||||||
Log.i(TAG, "end of file out");
|
Log.i(TAG, "end of file out");
|
||||||
if (loopMode) {
|
running = false;
|
||||||
loopFileInterface.onReset(false);
|
if (loopMode) {
|
||||||
} else {
|
loopFileInterface.onReset(false);
|
||||||
audioDecoderInterface.onAudioDecoderFinished();
|
} else {
|
||||||
}
|
audioDecoderInterface.onAudioDecoderFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getTime() {
|
/**
|
||||||
if (decoding) {
|
* This method should be called after prepare.
|
||||||
return audioExtractor.getSampleTime() / 10E5;
|
* Get max output size to set max input size in encoder.
|
||||||
|
*/
|
||||||
|
public int getOutsize() {
|
||||||
|
if (!(mime.equals(CodecUtil.AAC_MIME) || mime.equals(CodecUtil.OPUS_MIME) || mime.equals(
|
||||||
|
CodecUtil.VORBIS_MIME))) {
|
||||||
|
Log.i(TAG, "fixing input size");
|
||||||
|
try {
|
||||||
|
if (running) {
|
||||||
|
return codec.getOutputBuffers()[0].remaining();
|
||||||
|
} else {
|
||||||
|
if (codec != null) {
|
||||||
|
codec.start();
|
||||||
|
int outSize = codec.getOutputBuffers()[0].remaining();
|
||||||
|
stopDecoder();
|
||||||
|
if (prepare(null)) return outSize;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Log.i(TAG, "default input size");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void moveTo(double time) {
|
|
||||||
audioExtractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
||||||
seekTime = audioExtractor.getSampleTime() / 1000;
|
|
||||||
startMs = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoopMode(boolean loopMode) {
|
|
||||||
this.loopMode = loopMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mute() {
|
public void mute() {
|
||||||
muted = true;
|
muted = true;
|
||||||
}
|
}
|
||||||
|
@ -236,8 +214,4 @@ public class AudioDecoder {
|
||||||
public boolean isStereo() {
|
public boolean isStereo() {
|
||||||
return isStereo;
|
return isStereo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration() {
|
|
||||||
return duration / 10E5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.decoder;
|
package com.pedro.encoder.input.decoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.decoder;
|
package com.pedro.encoder.input.decoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,178 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.decoder;
|
package com.pedro.encoder.input.decoder;
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by pedro on 20/06/17.
|
* Created by pedro on 20/06/17.
|
||||||
*/
|
*/
|
||||||
public class VideoDecoder {
|
public class VideoDecoder extends BaseDecoder {
|
||||||
|
|
||||||
private final String TAG = "VideoDecoder";
|
|
||||||
|
|
||||||
private VideoDecoderInterface videoDecoderInterface;
|
private VideoDecoderInterface videoDecoderInterface;
|
||||||
private LoopFileInterface loopFileInterface;
|
|
||||||
private MediaExtractor videoExtractor;
|
|
||||||
private MediaCodec videoDecoder;
|
|
||||||
private MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();
|
|
||||||
private boolean decoding;
|
|
||||||
private Thread thread;
|
|
||||||
private MediaFormat videoFormat;
|
|
||||||
private String mime = "";
|
|
||||||
private int width;
|
private int width;
|
||||||
private int height;
|
private int height;
|
||||||
private long duration;
|
|
||||||
private static boolean loopMode = false;
|
|
||||||
private volatile long seekTime = 0;
|
|
||||||
private volatile long startMs = 0;
|
|
||||||
|
|
||||||
public VideoDecoder(VideoDecoderInterface videoDecoderInterface,
|
public VideoDecoder(VideoDecoderInterface videoDecoderInterface,
|
||||||
LoopFileInterface loopFileInterface) {
|
LoopFileInterface loopFileInterface) {
|
||||||
|
super(loopFileInterface);
|
||||||
|
TAG = "VideoDecoder";
|
||||||
this.videoDecoderInterface = videoDecoderInterface;
|
this.videoDecoderInterface = videoDecoderInterface;
|
||||||
this.loopFileInterface = loopFileInterface;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean initExtractor(String filePath) throws IOException {
|
@Override
|
||||||
decoding = false;
|
protected boolean extract(MediaExtractor videoExtractor) {
|
||||||
videoExtractor = new MediaExtractor();
|
running = false;
|
||||||
videoExtractor.setDataSource(filePath);
|
|
||||||
for (int i = 0; i < videoExtractor.getTrackCount() && !mime.startsWith("video/"); i++) {
|
for (int i = 0; i < videoExtractor.getTrackCount() && !mime.startsWith("video/"); i++) {
|
||||||
videoFormat = videoExtractor.getTrackFormat(i);
|
mediaFormat = videoExtractor.getTrackFormat(i);
|
||||||
mime = videoFormat.getString(MediaFormat.KEY_MIME);
|
mime = mediaFormat.getString(MediaFormat.KEY_MIME);
|
||||||
if (mime.startsWith("video/")) {
|
if (mime.startsWith("video/")) {
|
||||||
videoExtractor.selectTrack(i);
|
videoExtractor.selectTrack(i);
|
||||||
} else {
|
} else {
|
||||||
videoFormat = null;
|
mediaFormat = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (videoFormat != null) {
|
if (mediaFormat != null) {
|
||||||
width = videoFormat.getInteger(MediaFormat.KEY_WIDTH);
|
width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
|
||||||
height = videoFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
||||||
duration = videoFormat.getLong(MediaFormat.KEY_DURATION);
|
duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
|
||||||
return true;
|
return true;
|
||||||
//video decoder not supported
|
//video decoder not supported
|
||||||
} else {
|
} else {
|
||||||
mime = "";
|
mime = "";
|
||||||
videoFormat = null;
|
mediaFormat = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean prepareVideo(Surface surface) {
|
public boolean prepareVideo(Surface surface) {
|
||||||
try {
|
return prepare(surface);
|
||||||
videoDecoder = MediaCodec.createDecoderByType(mime);
|
|
||||||
videoDecoder.configure(videoFormat, surface, null, 0);
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Prepare decoder error:", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void reset(Surface surface) {
|
||||||
decoding = true;
|
resetCodec(surface);
|
||||||
videoDecoder.start();
|
|
||||||
thread = new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
decodeVideo();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.i(TAG, "Decoding error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
@Override
|
||||||
decoding = false;
|
protected void decode() {
|
||||||
seekTime = 0;
|
ByteBuffer[] inputBuffers = codec.getInputBuffers();
|
||||||
if (thread != null) {
|
|
||||||
thread.interrupt();
|
|
||||||
try {
|
|
||||||
thread.join(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
thread.interrupt();
|
|
||||||
}
|
|
||||||
thread = null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
videoDecoder.stop();
|
|
||||||
videoDecoder.release();
|
|
||||||
videoDecoder = null;
|
|
||||||
} catch (IllegalStateException | NullPointerException e) {
|
|
||||||
videoDecoder = null;
|
|
||||||
}
|
|
||||||
if (videoExtractor != null) {
|
|
||||||
videoExtractor.release();
|
|
||||||
videoExtractor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decodeVideo() throws IllegalStateException {
|
|
||||||
ByteBuffer[] inputBuffers = videoDecoder.getInputBuffers();
|
|
||||||
startMs = System.currentTimeMillis();
|
startMs = System.currentTimeMillis();
|
||||||
while (decoding) {
|
while (running) {
|
||||||
int inIndex = videoDecoder.dequeueInputBuffer(10000);
|
int inIndex = codec.dequeueInputBuffer(10000);
|
||||||
if (inIndex >= 0) {
|
if (inIndex >= 0) {
|
||||||
ByteBuffer buffer = inputBuffers[inIndex];
|
ByteBuffer buffer = inputBuffers[inIndex];
|
||||||
int sampleSize = videoExtractor.readSampleData(buffer, 0);
|
int sampleSize = extractor.readSampleData(buffer, 0);
|
||||||
if (sampleSize < 0) {
|
if (sampleSize < 0) {
|
||||||
videoDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||||
} else {
|
} else {
|
||||||
videoDecoder.queueInputBuffer(inIndex, 0, sampleSize, videoExtractor.getSampleTime(), 0);
|
codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
|
||||||
videoExtractor.advance();
|
extractor.advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int outIndex = videoDecoder.dequeueOutputBuffer(videoInfo, 10000);
|
int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
|
||||||
if (outIndex >= 0) {
|
if (outIndex >= 0) {
|
||||||
while (videoExtractor.getSampleTime() / 1000
|
long extractorTs = extractor.getSampleTime() / 1000;
|
||||||
> System.currentTimeMillis() - startMs + seekTime) {
|
long currentTs = System.currentTimeMillis() - startMs + seekTime;
|
||||||
|
if (extractorTs > currentTs) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(10);
|
long sleepTime = extractorTs - currentTs;
|
||||||
|
Thread.sleep(sleepTime);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
if (thread != null) thread.interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
videoDecoder.releaseOutputBuffer(outIndex, videoInfo.size != 0);
|
codec.releaseOutputBuffer(outIndex, bufferInfo.size != 0);
|
||||||
}
|
}
|
||||||
if ((videoInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
}
|
||||||
seekTime = 0;
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||||
Log.i(TAG, "end of file out");
|
seekTime = 0;
|
||||||
if (loopMode) {
|
Log.i(TAG, "end of file out");
|
||||||
loopFileInterface.onReset(true);
|
running = false;
|
||||||
} else {
|
if (loopMode) {
|
||||||
videoDecoderInterface.onVideoDecoderFinished();
|
loopFileInterface.onReset(true);
|
||||||
}
|
} else {
|
||||||
|
videoDecoderInterface.onVideoDecoderFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getTime() {
|
public void changeOutputSurface(Surface surface) {
|
||||||
if (decoding) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
return videoExtractor.getSampleTime() / 10E5;
|
codec.setOutputSurface(surface);
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
reset(surface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void moveTo(double time) {
|
|
||||||
videoExtractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
||||||
seekTime = videoExtractor.getSampleTime() / 1000;
|
|
||||||
startMs = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoopMode(boolean loopMode) {
|
|
||||||
this.loopMode = loopMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
@ -180,8 +132,4 @@ public class VideoDecoder {
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration() {
|
|
||||||
return duration / 10E5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.decoder;
|
package com.pedro.encoder.input.decoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl;
|
package com.pedro.encoder.input.gl;
|
||||||
|
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl;
|
package com.pedro.encoder.input.gl;
|
||||||
|
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
|
@ -5,6 +21,8 @@ import android.os.Build;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import com.pedro.encoder.input.gl.render.filters.AndroidViewFilterRender;
|
||||||
|
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
|
||||||
import com.pedro.encoder.input.gl.render.filters.object.BaseObjectFilterRender;
|
import com.pedro.encoder.input.gl.render.filters.object.BaseObjectFilterRender;
|
||||||
import com.pedro.encoder.input.video.CameraHelper;
|
import com.pedro.encoder.input.video.CameraHelper;
|
||||||
|
|
||||||
|
@ -16,6 +34,7 @@ import com.pedro.encoder.input.video.CameraHelper;
|
||||||
public class SpriteGestureController {
|
public class SpriteGestureController {
|
||||||
|
|
||||||
private BaseObjectFilterRender baseObjectFilterRender;
|
private BaseObjectFilterRender baseObjectFilterRender;
|
||||||
|
private AndroidViewFilterRender androidViewFilterRender;
|
||||||
private float lastDistance;
|
private float lastDistance;
|
||||||
private boolean preventMoveOutside = true;
|
private boolean preventMoveOutside = true;
|
||||||
|
|
||||||
|
@ -26,12 +45,27 @@ public class SpriteGestureController {
|
||||||
this.baseObjectFilterRender = sprite;
|
this.baseObjectFilterRender = sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseObjectFilterRender getBaseObjectFilterRender() {
|
public SpriteGestureController(AndroidViewFilterRender sprite) {
|
||||||
return baseObjectFilterRender;
|
this.androidViewFilterRender = sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseFilterRender getFilterRender() {
|
||||||
|
return androidViewFilterRender == null ? baseObjectFilterRender : androidViewFilterRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBaseObjectFilterRender(BaseObjectFilterRender baseObjectFilterRender) {
|
public void setBaseObjectFilterRender(BaseObjectFilterRender baseObjectFilterRender) {
|
||||||
this.baseObjectFilterRender = baseObjectFilterRender;
|
this.baseObjectFilterRender = baseObjectFilterRender;
|
||||||
|
this.androidViewFilterRender = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseObjectFilterRender(AndroidViewFilterRender androidViewFilterRender) {
|
||||||
|
this.androidViewFilterRender = androidViewFilterRender;
|
||||||
|
this.baseObjectFilterRender = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopListener() {
|
||||||
|
this.androidViewFilterRender = null;
|
||||||
|
this.baseObjectFilterRender = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreventMoveOutside(boolean preventMoveOutside) {
|
public void setPreventMoveOutside(boolean preventMoveOutside) {
|
||||||
|
@ -39,22 +73,34 @@ public class SpriteGestureController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean spriteTouched(View view, MotionEvent motionEvent) {
|
public boolean spriteTouched(View view, MotionEvent motionEvent) {
|
||||||
if (baseObjectFilterRender == null) return false;
|
if (baseObjectFilterRender == null && androidViewFilterRender == null) return false;
|
||||||
float xPercent = motionEvent.getX() * 100 / view.getWidth();
|
float xPercent = motionEvent.getX() * 100 / view.getWidth();
|
||||||
float yPercent = motionEvent.getY() * 100 / view.getHeight();
|
float yPercent = motionEvent.getY() * 100 / view.getHeight();
|
||||||
PointF scale = baseObjectFilterRender.getScale();
|
PointF scale;
|
||||||
PointF position = baseObjectFilterRender.getPosition();
|
PointF position;
|
||||||
|
if (baseObjectFilterRender != null) {
|
||||||
|
scale = baseObjectFilterRender.getScale();
|
||||||
|
position = baseObjectFilterRender.getPosition();
|
||||||
|
} else {
|
||||||
|
scale = androidViewFilterRender.getScale();
|
||||||
|
position = androidViewFilterRender.getPosition();
|
||||||
|
}
|
||||||
boolean xTouched = xPercent >= position.x && xPercent <= position.x + scale.x;
|
boolean xTouched = xPercent >= position.x && xPercent <= position.x + scale.x;
|
||||||
boolean yTouched = yPercent >= position.y && yPercent <= position.y + scale.y;
|
boolean yTouched = yPercent >= position.y && yPercent <= position.y + scale.y;
|
||||||
return xTouched && yTouched;
|
return xTouched && yTouched;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void moveSprite(View view, MotionEvent motionEvent) {
|
public void moveSprite(View view, MotionEvent motionEvent) {
|
||||||
if (baseObjectFilterRender == null) return;
|
if (baseObjectFilterRender == null && androidViewFilterRender == null) return;
|
||||||
if (motionEvent.getPointerCount() == 1) {
|
if (motionEvent.getPointerCount() == 1) {
|
||||||
float xPercent = motionEvent.getX() * 100 / view.getWidth();
|
float xPercent = motionEvent.getX() * 100 / view.getWidth();
|
||||||
float yPercent = motionEvent.getY() * 100 / view.getHeight();
|
float yPercent = motionEvent.getY() * 100 / view.getHeight();
|
||||||
PointF scale = baseObjectFilterRender.getScale();
|
PointF scale;
|
||||||
|
if (baseObjectFilterRender != null) {
|
||||||
|
scale = baseObjectFilterRender.getScale();
|
||||||
|
} else {
|
||||||
|
scale = androidViewFilterRender.getScale();
|
||||||
|
}
|
||||||
if (preventMoveOutside) {
|
if (preventMoveOutside) {
|
||||||
float x = xPercent - scale.x / 2.0F;
|
float x = xPercent - scale.x / 2.0F;
|
||||||
float y = yPercent - scale.y / 2.0F;
|
float y = yPercent - scale.y / 2.0F;
|
||||||
|
@ -70,22 +116,39 @@ public class SpriteGestureController {
|
||||||
if (y + scale.y > 100.0F) {
|
if (y + scale.y > 100.0F) {
|
||||||
y = 100.0F - scale.y;
|
y = 100.0F - scale.y;
|
||||||
}
|
}
|
||||||
baseObjectFilterRender.setPosition(x, y);
|
if (baseObjectFilterRender != null) {
|
||||||
|
baseObjectFilterRender.setPosition(x, y);
|
||||||
|
} else {
|
||||||
|
androidViewFilterRender.setPosition(x, y);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
baseObjectFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
|
if (baseObjectFilterRender != null) {
|
||||||
|
baseObjectFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
|
||||||
|
} else {
|
||||||
|
androidViewFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scaleSprite(MotionEvent motionEvent) {
|
public void scaleSprite(MotionEvent motionEvent) {
|
||||||
if (baseObjectFilterRender == null) return;
|
if (baseObjectFilterRender == null && androidViewFilterRender == null) return;
|
||||||
if (motionEvent.getPointerCount() > 1) {
|
if (motionEvent.getPointerCount() > 1) {
|
||||||
float distance = CameraHelper.getFingerSpacing(motionEvent);
|
float distance = CameraHelper.getFingerSpacing(motionEvent);
|
||||||
float percent = distance >= lastDistance ? 1 : -1;
|
float percent = distance >= lastDistance ? 1 : -1;
|
||||||
PointF scale = baseObjectFilterRender.getScale();
|
PointF scale;
|
||||||
|
if (baseObjectFilterRender != null) {
|
||||||
|
scale = baseObjectFilterRender.getScale();
|
||||||
|
} else {
|
||||||
|
scale = androidViewFilterRender.getScale();
|
||||||
|
}
|
||||||
scale.x += percent;
|
scale.x += percent;
|
||||||
scale.y += percent;
|
scale.y += percent;
|
||||||
baseObjectFilterRender.setScale(scale.x, scale.y);
|
if (baseObjectFilterRender != null) {
|
||||||
|
baseObjectFilterRender.setScale(scale.x, scale.y);
|
||||||
|
} else {
|
||||||
|
androidViewFilterRender.setScale(scale.x, scale.y);
|
||||||
|
}
|
||||||
lastDistance = distance;
|
lastDistance = distance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl;
|
package com.pedro.encoder.input.gl;
|
||||||
|
|
||||||
import android.opengl.EGL14;
|
import android.opengl.EGL14;
|
||||||
|
@ -19,45 +35,29 @@ import com.pedro.encoder.utils.gl.GlUtil;
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
public class SurfaceManager {
|
public class SurfaceManager {
|
||||||
|
|
||||||
|
private static final String TAG = "SurfaceManager";
|
||||||
|
|
||||||
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
|
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
|
||||||
|
|
||||||
private EGLContext eglContext = null;
|
private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
|
||||||
private EGLSurface eglSurface = null;
|
private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
|
||||||
private EGLDisplay eglDisplay = null;
|
private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
|
||||||
|
private volatile boolean isReady = false;
|
||||||
|
|
||||||
/**
|
public boolean isReady() {
|
||||||
* Creates an EGL context and an EGL surface.
|
return isReady;
|
||||||
*/
|
|
||||||
public SurfaceManager(Surface surface, SurfaceManager manager) {
|
|
||||||
eglSetup(surface, manager.eglContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an EGL context and an EGL surface.
|
|
||||||
*/
|
|
||||||
public SurfaceManager(Surface surface, EGLContext eglContext) {
|
|
||||||
eglSetup(surface, eglContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an EGL context and an EGL surface.
|
|
||||||
*/
|
|
||||||
public SurfaceManager(Surface surface) {
|
|
||||||
eglSetup(surface, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SurfaceManager() {
|
|
||||||
eglSetup(null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeCurrent() {
|
public void makeCurrent() {
|
||||||
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
|
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
|
||||||
Log.e("Error", "eglMakeCurrent failed");
|
Log.e(TAG, "eglMakeCurrent failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapBuffer() {
|
public void swapBuffer() {
|
||||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
if (!EGL14.eglSwapBuffers(eglDisplay, eglSurface)) {
|
||||||
|
Log.e(TAG, "eglSwapBuffers failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,7 +71,11 @@ public class SurfaceManager {
|
||||||
/**
|
/**
|
||||||
* Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
|
* Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
|
||||||
*/
|
*/
|
||||||
private void eglSetup(Surface surface, EGLContext eglSharedContext) {
|
public void eglSetup(int width, int height, Surface surface, EGLContext eglSharedContext) {
|
||||||
|
if (isReady) {
|
||||||
|
Log.e(TAG, "already ready, ignored");
|
||||||
|
return;
|
||||||
|
}
|
||||||
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||||
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
|
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
|
||||||
throw new RuntimeException("unable to get EGL14 display");
|
throw new RuntimeException("unable to get EGL14 display");
|
||||||
|
@ -83,23 +87,43 @@ public class SurfaceManager {
|
||||||
|
|
||||||
// Configure EGL for recording and OpenGL ES 2.0.
|
// Configure EGL for recording and OpenGL ES 2.0.
|
||||||
int[] attribList;
|
int[] attribList;
|
||||||
if (eglSharedContext == null) {
|
if (eglSharedContext == null && surface == null) {
|
||||||
|
attribList = new int[]{
|
||||||
|
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
|
||||||
|
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
|
||||||
|
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||||
|
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
|
||||||
|
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
|
||||||
|
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
|
||||||
|
EGL14.EGL_NONE
|
||||||
|
};
|
||||||
|
} else if (eglSharedContext == null) {
|
||||||
|
attribList = new int[]{
|
||||||
|
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
|
||||||
|
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||||
|
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
|
||||||
|
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
|
||||||
|
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
|
||||||
|
EGL14.EGL_NONE
|
||||||
|
};
|
||||||
|
} else if (surface == null) {
|
||||||
attribList = new int[] {
|
attribList = new int[] {
|
||||||
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
|
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
|
||||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
|
||||||
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
|
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
|
||||||
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
|
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
|
||||||
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
|
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
|
||||||
EGL14.EGL_NONE
|
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
|
||||||
|
EGL14.EGL_NONE
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
attribList = new int[] {
|
attribList = new int[] {
|
||||||
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
|
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
|
||||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
|
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
|
||||||
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
|
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
|
||||||
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
|
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
|
||||||
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
|
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
|
||||||
EGL14.EGL_NONE
|
EGL14.EGL_NONE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
EGLConfig[] configs = new EGLConfig[1];
|
EGLConfig[] configs = new EGLConfig[1];
|
||||||
|
@ -119,7 +143,7 @@ public class SurfaceManager {
|
||||||
// Create a window surface, and attach it to the Surface we received.
|
// Create a window surface, and attach it to the Surface we received.
|
||||||
if (surface == null) {
|
if (surface == null) {
|
||||||
int[] surfaceAttribs = {
|
int[] surfaceAttribs = {
|
||||||
EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE
|
EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE
|
||||||
};
|
};
|
||||||
eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs, 0);
|
eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs, 0);
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,6 +153,28 @@ public class SurfaceManager {
|
||||||
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0);
|
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0);
|
||||||
}
|
}
|
||||||
GlUtil.checkEglError("eglCreateWindowSurface");
|
GlUtil.checkEglError("eglCreateWindowSurface");
|
||||||
|
isReady = true;
|
||||||
|
Log.i(TAG, "GL initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eglSetup(Surface surface, SurfaceManager manager) {
|
||||||
|
eglSetup(2, 2, surface, manager.eglContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eglSetup(int width, int height, SurfaceManager manager) {
|
||||||
|
eglSetup(width, height, null, manager.eglContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eglSetup(Surface surface, EGLContext eglContext) {
|
||||||
|
eglSetup(2, 2, surface, eglContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eglSetup(Surface surface) {
|
||||||
|
eglSetup(2, 2, surface, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void eglSetup() {
|
||||||
|
eglSetup(2, 2, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -142,10 +188,14 @@ public class SurfaceManager {
|
||||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||||
EGL14.eglReleaseThread();
|
EGL14.eglReleaseThread();
|
||||||
EGL14.eglTerminate(eglDisplay);
|
EGL14.eglTerminate(eglDisplay);
|
||||||
|
Log.i(TAG, "GL released");
|
||||||
|
eglDisplay = EGL14.EGL_NO_DISPLAY;
|
||||||
|
eglContext = EGL14.EGL_NO_CONTEXT;
|
||||||
|
eglSurface = EGL14.EGL_NO_SURFACE;
|
||||||
|
isReady = false;
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "GL already released");
|
||||||
}
|
}
|
||||||
eglDisplay = EGL14.EGL_NO_DISPLAY;
|
|
||||||
eglContext = EGL14.EGL_NO_CONTEXT;
|
|
||||||
eglSurface = EGL14.EGL_NO_SURFACE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EGLContext getEglContext() {
|
public EGLContext getEglContext() {
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl;
|
package com.pedro.encoder.input.gl;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render;
|
package com.pedro.encoder.input.gl.render;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render;
|
package com.pedro.encoder.input.gl.render;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -63,7 +79,7 @@ public class CameraRender extends BaseRenderOffScreen {
|
||||||
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
|
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
|
||||||
|
|
||||||
//camera texture
|
//camera texture
|
||||||
GlUtil.createExternalTextures(1, textureID, 0);
|
GlUtil.createExternalTextures(textureID.length, textureID, 0);
|
||||||
surfaceTexture = new SurfaceTexture(textureID[0]);
|
surfaceTexture = new SurfaceTexture(textureID[0]);
|
||||||
surfaceTexture.setDefaultBufferSize(width, height);
|
surfaceTexture.setDefaultBufferSize(width, height);
|
||||||
surface = new Surface(surfaceTexture);
|
surface = new Surface(surfaceTexture);
|
||||||
|
@ -107,8 +123,8 @@ public class CameraRender extends BaseRenderOffScreen {
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
GLES20.glDeleteProgram(program);
|
GLES20.glDeleteProgram(program);
|
||||||
surfaceTexture = null;
|
surfaceTexture.release();
|
||||||
surface = null;
|
surface.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateTexImage() {
|
public void updateTexImage() {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render;
|
package com.pedro.encoder.input.gl.render;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -5,8 +21,9 @@ import android.graphics.SurfaceTexture;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import com.pedro.encoder.input.gl.FilterAction;
|
||||||
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
|
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
|
||||||
import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -17,13 +34,12 @@ import java.util.List;
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
public class ManagerRender {
|
public class ManagerRender {
|
||||||
|
|
||||||
//Increase it to render more than 1 filter and set filter by position.
|
//Set filter limit. If the number is 0 or less you can add infinity filters
|
||||||
// You must modify it before create your rtmp or rtsp object.
|
public static int numFilters = 0;
|
||||||
public static int numFilters = 1;
|
|
||||||
|
|
||||||
private CameraRender cameraRender;
|
private final CameraRender cameraRender;
|
||||||
private List<BaseFilterRender> baseFilterRender = new ArrayList<>(numFilters);
|
private final List<BaseFilterRender> filterRenders;
|
||||||
private ScreenRender screenRender;
|
private final ScreenRender screenRender;
|
||||||
|
|
||||||
private int width;
|
private int width;
|
||||||
private int height;
|
private int height;
|
||||||
|
@ -32,8 +48,8 @@ public class ManagerRender {
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
public ManagerRender() {
|
public ManagerRender() {
|
||||||
|
filterRenders = new ArrayList<>();
|
||||||
cameraRender = new CameraRender();
|
cameraRender = new CameraRender();
|
||||||
for (int i = 0; i < numFilters; i++) baseFilterRender.add(new NoFilterRender());
|
|
||||||
screenRender = new ScreenRender();
|
screenRender = new ScreenRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,33 +61,26 @@ public class ManagerRender {
|
||||||
this.previewWidth = previewWidth;
|
this.previewWidth = previewWidth;
|
||||||
this.previewHeight = previewHeight;
|
this.previewHeight = previewHeight;
|
||||||
cameraRender.initGl(width, height, context, previewWidth, previewHeight);
|
cameraRender.initGl(width, height, context, previewWidth, previewHeight);
|
||||||
for (int i = 0; i < numFilters; i++) {
|
|
||||||
int textId = i == 0 ? cameraRender.getTexId() : baseFilterRender.get(i - 1).getTexId();
|
|
||||||
baseFilterRender.get(i).setPreviousTexId(textId);
|
|
||||||
baseFilterRender.get(i).initGl(width, height, context, previewWidth, previewHeight);
|
|
||||||
baseFilterRender.get(i).initFBOLink();
|
|
||||||
}
|
|
||||||
screenRender.setStreamSize(encoderWidth, encoderHeight);
|
screenRender.setStreamSize(encoderWidth, encoderHeight);
|
||||||
screenRender.setTexId(baseFilterRender.get(numFilters - 1).getTexId());
|
screenRender.setTexId(cameraRender.getTexId());
|
||||||
screenRender.initGl(context);
|
screenRender.initGl(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawOffScreen() {
|
public void drawOffScreen() {
|
||||||
cameraRender.draw();
|
cameraRender.draw();
|
||||||
for (BaseFilterRender baseFilterRender : baseFilterRender) baseFilterRender.draw();
|
for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawScreen(int width, int height, boolean keepAspectRatio, int mode, int rotation,
|
public void drawScreen(int width, int height, boolean keepAspectRatio, int mode, int rotation,
|
||||||
boolean isPreview) {
|
boolean flipStreamVertical, boolean flipStreamHorizontal) {
|
||||||
screenRender.draw(width, height, keepAspectRatio, mode, rotation, isPreview);
|
screenRender.draw(width, height, keepAspectRatio, mode, rotation, flipStreamVertical,
|
||||||
|
flipStreamHorizontal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
cameraRender.release();
|
cameraRender.release();
|
||||||
for (int i = 0; i < this.baseFilterRender.size(); i++) {
|
for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.release();
|
||||||
this.baseFilterRender.get(i).release();
|
filterRenders.clear();
|
||||||
this.baseFilterRender.set(i, new NoFilterRender());
|
|
||||||
}
|
|
||||||
screenRender.release();
|
screenRender.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,14 +104,99 @@ public class ManagerRender {
|
||||||
return cameraRender.getSurface();
|
return cameraRender.getSurface();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFilter(int position, BaseFilterRender baseFilterRender) {
|
private void setFilter(int position, BaseFilterRender baseFilterRender) {
|
||||||
final int id = this.baseFilterRender.get(position).getPreviousTexId();
|
final int id = filterRenders.get(position).getPreviousTexId();
|
||||||
final RenderHandler renderHandler = this.baseFilterRender.get(position).getRenderHandler();
|
final RenderHandler renderHandler = filterRenders.get(position).getRenderHandler();
|
||||||
this.baseFilterRender.get(position).release();
|
filterRenders.get(position).release();
|
||||||
this.baseFilterRender.set(position, baseFilterRender);
|
filterRenders.set(position, baseFilterRender);
|
||||||
this.baseFilterRender.get(position).setPreviousTexId(id);
|
filterRenders.get(position).setPreviousTexId(id);
|
||||||
this.baseFilterRender.get(position).initGl(width, height, context, previewWidth, previewHeight);
|
filterRenders.get(position).initGl(width, height, context, previewWidth, previewHeight);
|
||||||
this.baseFilterRender.get(position).setRenderHandler(renderHandler);
|
filterRenders.get(position).setRenderHandler(renderHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFilter(BaseFilterRender baseFilterRender) {
|
||||||
|
filterRenders.add(baseFilterRender);
|
||||||
|
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight);
|
||||||
|
baseFilterRender.initFBOLink();
|
||||||
|
reOrderFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFilter(int position, BaseFilterRender baseFilterRender) {
|
||||||
|
filterRenders.add(position, baseFilterRender);
|
||||||
|
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight);
|
||||||
|
baseFilterRender.initFBOLink();
|
||||||
|
reOrderFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearFilters() {
|
||||||
|
for (BaseFilterRender baseFilterRender: filterRenders) {
|
||||||
|
baseFilterRender.release();
|
||||||
|
}
|
||||||
|
filterRenders.clear();
|
||||||
|
reOrderFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFilter(int position) {
|
||||||
|
filterRenders.remove(position).release();
|
||||||
|
reOrderFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFilter(BaseFilterRender baseFilterRender) {
|
||||||
|
baseFilterRender.release();
|
||||||
|
filterRenders.remove(baseFilterRender);
|
||||||
|
reOrderFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reOrderFilters() {
|
||||||
|
for (int i = 0; i < filterRenders.size(); i++) {
|
||||||
|
int texId = i == 0 ? cameraRender.getTexId() : filterRenders.get(i - 1).getTexId();
|
||||||
|
filterRenders.get(i).setPreviousTexId(texId);
|
||||||
|
}
|
||||||
|
int texId = filterRenders.isEmpty() ? cameraRender.getTexId() :
|
||||||
|
filterRenders.get(filterRenders.size() - 1).getTexId();
|
||||||
|
screenRender.setTexId(texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilterAction(FilterAction filterAction, int position, BaseFilterRender baseFilterRender) {
|
||||||
|
switch (filterAction) {
|
||||||
|
case SET:
|
||||||
|
if (filterRenders.size() > 0) {
|
||||||
|
setFilter(position, baseFilterRender);
|
||||||
|
} else {
|
||||||
|
addFilter(baseFilterRender);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SET_INDEX:
|
||||||
|
setFilter(position, baseFilterRender);
|
||||||
|
break;
|
||||||
|
case ADD:
|
||||||
|
if (numFilters > 0 && filterRenders.size() >= numFilters) {
|
||||||
|
throw new RuntimeException("limit of filters(" + numFilters + ") exceeded");
|
||||||
|
}
|
||||||
|
addFilter(baseFilterRender);
|
||||||
|
break;
|
||||||
|
case ADD_INDEX:
|
||||||
|
if (numFilters > 0 && filterRenders.size() >= numFilters) {
|
||||||
|
throw new RuntimeException("limit of filters(" + numFilters + ") exceeded");
|
||||||
|
}
|
||||||
|
addFilter(position, baseFilterRender);
|
||||||
|
break;
|
||||||
|
case CLEAR:
|
||||||
|
clearFilters();
|
||||||
|
break;
|
||||||
|
case REMOVE:
|
||||||
|
removeFilter(baseFilterRender);
|
||||||
|
break;
|
||||||
|
case REMOVE_INDEX:
|
||||||
|
removeFilter(position);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int filtersCount() {
|
||||||
|
return filterRenders.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCameraRotation(int rotation) {
|
public void setCameraRotation(int rotation) {
|
||||||
|
@ -114,8 +208,8 @@ public class ManagerRender {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreviewSize(int previewWidth, int previewHeight) {
|
public void setPreviewSize(int previewWidth, int previewHeight) {
|
||||||
for (int i = 0; i < this.baseFilterRender.size(); i++) {
|
for (int i = 0; i < filterRenders.size(); i++) {
|
||||||
this.baseFilterRender.get(i).setPreviewSize(previewWidth, previewHeight);
|
filterRenders.get(i).setPreviewSize(previewWidth, previewHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render;
|
package com.pedro.encoder.input.gl.render;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render;
|
package com.pedro.encoder.input.gl.render;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -22,11 +38,11 @@ public class ScreenRender {
|
||||||
|
|
||||||
//rotation matrix
|
//rotation matrix
|
||||||
private final float[] squareVertexData = {
|
private final float[] squareVertexData = {
|
||||||
// X, Y, Z, U, V
|
// X, Y, Z, U, V
|
||||||
-1f, -1f, 0f, 0f, 0f, //bottom left
|
-1f, -1f, 0f, 0f, 0f, //bottom left
|
||||||
1f, -1f, 0f, 1f, 0f, //bottom right
|
1f, -1f, 0f, 1f, 0f, //bottom right
|
||||||
-1f, 1f, 0f, 0f, 1f, //top left
|
-1f, 1f, 0f, 0f, 1f, //top left
|
||||||
1f, 1f, 0f, 1f, 1f, //top right
|
1f, 1f, 0f, 1f, 1f, //top right
|
||||||
};
|
};
|
||||||
|
|
||||||
private FloatBuffer squareVertex;
|
private FloatBuffer squareVertex;
|
||||||
|
@ -48,20 +64,18 @@ public class ScreenRender {
|
||||||
|
|
||||||
private int streamWidth;
|
private int streamWidth;
|
||||||
private int streamHeight;
|
private int streamHeight;
|
||||||
private boolean isPortrait;
|
|
||||||
|
|
||||||
public ScreenRender() {
|
public ScreenRender() {
|
||||||
squareVertex =
|
squareVertex =
|
||||||
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
|
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
|
||||||
.order(ByteOrder.nativeOrder())
|
.order(ByteOrder.nativeOrder())
|
||||||
.asFloatBuffer();
|
.asFloatBuffer();
|
||||||
squareVertex.put(squareVertexData).position(0);
|
squareVertex.put(squareVertexData).position(0);
|
||||||
Matrix.setIdentityM(MVPMatrix, 0);
|
Matrix.setIdentityM(MVPMatrix, 0);
|
||||||
Matrix.setIdentityM(STMatrix, 0);
|
Matrix.setIdentityM(STMatrix, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initGl(Context context) {
|
public void initGl(Context context) {
|
||||||
isPortrait = CameraHelper.isPortrait(context);
|
|
||||||
GlUtil.checkGlError("initGl start");
|
GlUtil.checkGlError("initGl start");
|
||||||
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex);
|
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex);
|
||||||
String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.fxaa);
|
String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.fxaa);
|
||||||
|
@ -78,15 +92,47 @@ public class ScreenRender {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(int width, int height, boolean keepAspectRatio, int mode, int rotation,
|
public void draw(int width, int height, boolean keepAspectRatio, int mode, int rotation,
|
||||||
boolean isPreview) {
|
boolean flipStreamVertical, boolean flipStreamHorizontal) {
|
||||||
GlUtil.checkGlError("drawScreen start");
|
GlUtil.checkGlError("drawScreen start");
|
||||||
|
|
||||||
if (mode == 2 || mode == 3) { //stream rotation is enabled
|
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width,height,true,keepAspectRatio, MVPMatrix);
|
||||||
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
|
|
||||||
}
|
|
||||||
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
|
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
|
||||||
streamHeight);
|
streamHeight);
|
||||||
|
|
||||||
|
draw(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawEncoder(int width, int height, boolean isPortrait, int rotation,
|
||||||
|
boolean flipStreamVertical, boolean flipStreamHorizontal) {
|
||||||
|
GlUtil.checkGlError("drawScreen start");
|
||||||
|
|
||||||
|
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width, height, isPortrait,false, MVPMatrix);
|
||||||
|
SizeCalculator.calculateViewPortEncoder(width, height, isPortrait);
|
||||||
|
|
||||||
|
draw(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawPreview(int width, int height, boolean isPortrait, boolean keepAspectRatio,
|
||||||
|
int mode, int rotation, boolean flipStreamVertical, boolean flipStreamHorizontal) {
|
||||||
|
GlUtil.checkGlError("drawScreen start");
|
||||||
|
|
||||||
|
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, isPortrait,true, MVPMatrix);
|
||||||
|
float factor = (float) streamWidth / (float) streamHeight;
|
||||||
|
int w;
|
||||||
|
int h;
|
||||||
|
if (factor >= 1f) {
|
||||||
|
w = isPortrait ? streamHeight : streamWidth;
|
||||||
|
h = isPortrait ? streamWidth : streamHeight;
|
||||||
|
} else {
|
||||||
|
w = isPortrait ? streamWidth : streamHeight;
|
||||||
|
h = isPortrait ? streamHeight : streamWidth;
|
||||||
|
}
|
||||||
|
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, w, h);
|
||||||
|
|
||||||
|
draw(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void draw(int width, int height) {
|
||||||
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
|
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
@ -94,12 +140,12 @@ public class ScreenRender {
|
||||||
|
|
||||||
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_POS_OFFSET);
|
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_POS_OFFSET);
|
||||||
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
|
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
|
||||||
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
|
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
|
||||||
GLES20.glEnableVertexAttribArray(aPositionHandle);
|
GLES20.glEnableVertexAttribArray(aPositionHandle);
|
||||||
|
|
||||||
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_UV_OFFSET);
|
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_UV_OFFSET);
|
||||||
GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
|
GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
|
||||||
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
|
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
|
||||||
GLES20.glEnableVertexAttribArray(aTextureHandle);
|
GLES20.glEnableVertexAttribArray(aTextureHandle);
|
||||||
|
|
||||||
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);
|
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render;
|
package com.pedro.encoder.input.gl.render;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -49,7 +65,6 @@ public class SimpleCameraRender {
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
private int streamWidth;
|
private int streamWidth;
|
||||||
private int streamHeight;
|
private int streamHeight;
|
||||||
private boolean isPortrait;
|
|
||||||
|
|
||||||
public SimpleCameraRender() {
|
public SimpleCameraRender() {
|
||||||
Matrix.setIdentityM(MVPMatrix, 0);
|
Matrix.setIdentityM(MVPMatrix, 0);
|
||||||
|
@ -86,13 +101,11 @@ public class SimpleCameraRender {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawFrame(int width, int height, boolean keepAspectRatio, int mode, int rotation,
|
public void drawFrame(int width, int height, boolean keepAspectRatio, int mode, int rotation,
|
||||||
boolean isPreview) {
|
boolean flipStreamVertical, boolean flipStreamHorizontal) {
|
||||||
GlUtil.checkGlError("drawFrame start");
|
GlUtil.checkGlError("drawFrame start");
|
||||||
surfaceTexture.getTransformMatrix(STMatrix);
|
surfaceTexture.getTransformMatrix(STMatrix);
|
||||||
|
|
||||||
if (mode == 2 || mode == 3) { //stream rotation is enabled
|
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, true, false, MVPMatrix);
|
||||||
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
|
|
||||||
}
|
|
||||||
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
|
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
|
||||||
streamHeight);
|
streamHeight);
|
||||||
|
|
||||||
|
@ -125,7 +138,6 @@ public class SimpleCameraRender {
|
||||||
* Initializes GL state. Call this after the EGL surface has been created and made current.
|
* Initializes GL state. Call this after the EGL surface has been created and made current.
|
||||||
*/
|
*/
|
||||||
public void initGl(Context context, int streamWidth, int streamHeight) {
|
public void initGl(Context context, int streamWidth, int streamHeight) {
|
||||||
isPortrait = CameraHelper.isPortrait(context);
|
|
||||||
this.streamWidth = streamWidth;
|
this.streamWidth = streamWidth;
|
||||||
this.streamHeight = streamHeight;
|
this.streamHeight = streamHeight;
|
||||||
GlUtil.checkGlError("initGl start");
|
GlUtil.checkGlError("initGl start");
|
||||||
|
@ -139,7 +151,7 @@ public class SimpleCameraRender {
|
||||||
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
|
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
|
||||||
|
|
||||||
//camera texture
|
//camera texture
|
||||||
GlUtil.createExternalTextures(1, texturesID, 0);
|
GlUtil.createExternalTextures(texturesID.length, texturesID, 0);
|
||||||
textureID = texturesID[0];
|
textureID = texturesID[0];
|
||||||
|
|
||||||
surfaceTexture = new SurfaceTexture(textureID);
|
surfaceTexture = new SurfaceTexture(textureID);
|
||||||
|
@ -150,8 +162,8 @@ public class SimpleCameraRender {
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
GLES20.glDeleteProgram(program);
|
GLES20.glDeleteProgram(program);
|
||||||
surfaceTexture = null;
|
surfaceTexture.release();
|
||||||
surface = null;
|
surface.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlip(boolean isFlipHorizontal, boolean isFlipVertical) {
|
public void setFlip(boolean isFlipHorizontal, boolean isFlipVertical) {
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,8 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PointF;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.opengl.GLES11Ext;
|
import android.opengl.GLES11Ext;
|
||||||
|
@ -15,10 +32,13 @@ import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import com.pedro.encoder.R;
|
import com.pedro.encoder.R;
|
||||||
|
import com.pedro.encoder.input.gl.AndroidViewSprite;
|
||||||
import com.pedro.encoder.utils.gl.GlUtil;
|
import com.pedro.encoder.utils.gl.GlUtil;
|
||||||
import com.pedro.encoder.utils.gl.TranslateTo;
|
import com.pedro.encoder.utils.gl.TranslateTo;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by pedro on 4/02/18.
|
* Created by pedro on 4/02/18.
|
||||||
|
@ -44,16 +64,21 @@ public class AndroidViewFilterRender extends BaseFilterRender {
|
||||||
private int uSamplerHandle = -1;
|
private int uSamplerHandle = -1;
|
||||||
private int uSamplerViewHandle = -1;
|
private int uSamplerViewHandle = -1;
|
||||||
|
|
||||||
private int[] viewId = new int[1];
|
private int[] viewId = new int[] { -1, -1 };
|
||||||
private View view;
|
private View view;
|
||||||
private SurfaceTexture surfaceTexture;
|
//Use 2 surfaces to avoid block render thread
|
||||||
private Surface surface;
|
private SurfaceTexture surfaceTexture, surfaceTexture2;
|
||||||
private Handler mainHandler;
|
private Surface surface, surface2;
|
||||||
|
private final Handler mainHandler;
|
||||||
|
private boolean running = false;
|
||||||
|
private ExecutorService thread = null;
|
||||||
|
private boolean hardwareMode = true;
|
||||||
|
private final AndroidViewSprite sprite;
|
||||||
|
private volatile Status renderingStatus = Status.DONE1;
|
||||||
|
|
||||||
private int rotation;
|
private enum Status {
|
||||||
private float positionX, positionY;
|
RENDER1, RENDER2, DONE1, DONE2
|
||||||
private float scaleX = 1f, scaleY = 1f;
|
}
|
||||||
private float viewX, viewY;
|
|
||||||
|
|
||||||
public AndroidViewFilterRender() {
|
public AndroidViewFilterRender() {
|
||||||
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
|
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
|
||||||
|
@ -62,6 +87,7 @@ public class AndroidViewFilterRender extends BaseFilterRender {
|
||||||
squareVertex.put(squareVertexDataFilter).position(0);
|
squareVertex.put(squareVertexDataFilter).position(0);
|
||||||
Matrix.setIdentityM(MVPMatrix, 0);
|
Matrix.setIdentityM(MVPMatrix, 0);
|
||||||
Matrix.setIdentityM(STMatrix, 0);
|
Matrix.setIdentityM(STMatrix, 0);
|
||||||
|
sprite = new AndroidViewSprite();
|
||||||
mainHandler = new Handler(Looper.getMainLooper());
|
mainHandler = new Handler(Looper.getMainLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,31 +104,37 @@ public class AndroidViewFilterRender extends BaseFilterRender {
|
||||||
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
|
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
|
||||||
uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView");
|
uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView");
|
||||||
|
|
||||||
GlUtil.createExternalTextures(1, viewId, 0);
|
GlUtil.createExternalTextures(viewId.length, viewId, 0);
|
||||||
surfaceTexture = new SurfaceTexture(viewId[0]);
|
surfaceTexture = new SurfaceTexture(viewId[0]);
|
||||||
|
surfaceTexture2 = new SurfaceTexture(viewId[1]);
|
||||||
surface = new Surface(surfaceTexture);
|
surface = new Surface(surfaceTexture);
|
||||||
|
surface2 = new Surface(surfaceTexture2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void drawFilter() {
|
protected void drawFilter() {
|
||||||
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
|
final Status status = renderingStatus;
|
||||||
if (view != null) {
|
switch (status) {
|
||||||
mainHandler.post(new Runnable() {
|
case DONE1:
|
||||||
@Override
|
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
|
||||||
public void run() {
|
surfaceTexture.updateTexImage();
|
||||||
Canvas canvas = surface.lockCanvas(null);
|
renderingStatus = Status.RENDER2;
|
||||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
break;
|
||||||
canvas.translate(positionX, positionY);
|
case DONE2:
|
||||||
canvas.rotate(rotation, viewX / 2f, viewY / 2f);
|
surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
|
||||||
float scaleFactorX = (float) getPreviewWidth() / (float) view.getWidth();
|
surfaceTexture2.updateTexImage();
|
||||||
float scaleFactorY = (float) getPreviewHeight() / (float) view.getHeight();
|
renderingStatus = Status.RENDER1;
|
||||||
canvas.scale(scaleX * scaleFactorX, scaleY * scaleFactorY);
|
break;
|
||||||
view.draw(canvas);
|
case RENDER1:
|
||||||
surface.unlockCanvasAndPost(canvas);
|
surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
|
||||||
}
|
surfaceTexture2.updateTexImage();
|
||||||
});
|
break;
|
||||||
|
case RENDER2:
|
||||||
|
default:
|
||||||
|
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
|
||||||
|
surfaceTexture.updateTexImage();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
surfaceTexture.updateTexImage();
|
|
||||||
|
|
||||||
GLES20.glUseProgram(program);
|
GLES20.glUseProgram(program);
|
||||||
|
|
||||||
|
@ -125,12 +157,27 @@ public class AndroidViewFilterRender extends BaseFilterRender {
|
||||||
//android view
|
//android view
|
||||||
GLES20.glUniform1i(uSamplerViewHandle, 5);
|
GLES20.glUniform1i(uSamplerViewHandle, 5);
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE5);
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE5);
|
||||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
|
|
||||||
|
switch (status) {
|
||||||
|
case DONE2:
|
||||||
|
case RENDER1:
|
||||||
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[1]);
|
||||||
|
break;
|
||||||
|
case RENDER2:
|
||||||
|
case DONE1:
|
||||||
|
default:
|
||||||
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
|
stopRender();
|
||||||
GLES20.glDeleteProgram(program);
|
GLES20.glDeleteProgram(program);
|
||||||
|
viewId = new int[] { -1, -1 };
|
||||||
|
surfaceTexture.release();
|
||||||
|
surfaceTexture2.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public View getView() {
|
public View getView() {
|
||||||
|
@ -138,11 +185,12 @@ public class AndroidViewFilterRender extends BaseFilterRender {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setView(final View view) {
|
public void setView(final View view) {
|
||||||
|
stopRender();
|
||||||
this.view = view;
|
this.view = view;
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||||
viewX = view.getMeasuredWidth();
|
sprite.setView(view);
|
||||||
viewY = view.getMeasuredHeight();
|
startRender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,68 +200,105 @@ public class AndroidViewFilterRender extends BaseFilterRender {
|
||||||
* @param y Position in percent
|
* @param y Position in percent
|
||||||
*/
|
*/
|
||||||
public void setPosition(float x, float y) {
|
public void setPosition(float x, float y) {
|
||||||
int previewX = getPreviewWidth();
|
sprite.translate(x, y);
|
||||||
int previewY = getPreviewHeight();
|
|
||||||
this.positionX = previewX * x / 100f;
|
|
||||||
this.positionY = previewY * y / 100f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPosition(TranslateTo positionTo) {
|
public void setPosition(TranslateTo positionTo) {
|
||||||
int previewX = getPreviewWidth();
|
sprite.translate(positionTo);
|
||||||
int previewY = getPreviewHeight();
|
|
||||||
switch (positionTo) {
|
|
||||||
case TOP:
|
|
||||||
this.positionX = previewX / 2f - (viewX / 2f);
|
|
||||||
this.positionY = 0f;
|
|
||||||
break;
|
|
||||||
case LEFT:
|
|
||||||
this.positionX = 0;
|
|
||||||
this.positionY = previewY / 2f - (viewY / 2f);
|
|
||||||
break;
|
|
||||||
case RIGHT:
|
|
||||||
this.positionX = previewX - viewX;
|
|
||||||
this.positionY = previewY / 2f - (viewY / 2f);
|
|
||||||
break;
|
|
||||||
case BOTTOM:
|
|
||||||
this.positionX = previewX / 2f - (viewX / 2f);
|
|
||||||
this.positionY = previewY - viewY;
|
|
||||||
break;
|
|
||||||
case CENTER:
|
|
||||||
this.positionX = previewX / 2f - (viewX / 2f);
|
|
||||||
this.positionY = previewY / 2f - (viewY / 2f);
|
|
||||||
break;
|
|
||||||
case TOP_RIGHT:
|
|
||||||
this.positionX = previewX - viewX;
|
|
||||||
this.positionY = 0;
|
|
||||||
break;
|
|
||||||
case BOTTOM_LEFT:
|
|
||||||
this.positionX = 0;
|
|
||||||
this.positionY = previewY - viewY;
|
|
||||||
break;
|
|
||||||
case BOTTOM_RIGHT:
|
|
||||||
this.positionX = previewX - viewX;
|
|
||||||
this.positionY = previewY - viewY;
|
|
||||||
break;
|
|
||||||
case TOP_LEFT:
|
|
||||||
default:
|
|
||||||
this.positionX = 0;
|
|
||||||
this.positionY = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRotation(int rotation) {
|
public void setRotation(int rotation) {
|
||||||
if (rotation < 0) {
|
sprite.setRotation(rotation);
|
||||||
this.rotation = 0;
|
|
||||||
} else if (rotation > 360) {
|
|
||||||
this.rotation = 360;
|
|
||||||
} else {
|
|
||||||
this.rotation = rotation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScale(float scaleX, float scaleY) {
|
public void setScale(float scaleX, float scaleY) {
|
||||||
this.scaleX = scaleX;
|
sprite.scale(scaleX, scaleY);
|
||||||
this.scaleY = scaleY;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public PointF getScale() {
|
||||||
|
return sprite.getScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointF getPosition() {
|
||||||
|
return sprite.getTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRotation() {
|
||||||
|
return sprite.getRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHardwareMode() {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hardwareMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw in surface using hardware canvas. True by default
|
||||||
|
*/
|
||||||
|
public void setHardwareMode(boolean hardwareMode) {
|
||||||
|
this.hardwareMode = hardwareMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startRender() {
|
||||||
|
running = true;
|
||||||
|
thread = Executors.newSingleThreadExecutor();
|
||||||
|
thread.execute(() -> {
|
||||||
|
while (running) {
|
||||||
|
final Status status = renderingStatus;
|
||||||
|
if (status == Status.RENDER1 || status == Status.RENDER2) {
|
||||||
|
final Canvas canvas;
|
||||||
|
try {
|
||||||
|
if (isHardwareMode()) {
|
||||||
|
canvas = status == Status.RENDER1 ? surface.lockHardwareCanvas() : surface2.lockHardwareCanvas();
|
||||||
|
} else {
|
||||||
|
canvas = status == Status.RENDER1 ? surface.lockCanvas(null) : surface2.lockCanvas(null);
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.calculateDefaultScale(getPreviewWidth(), getPreviewHeight());
|
||||||
|
PointF canvasPosition = sprite.getCanvasPosition(getPreviewWidth(), getPreviewHeight());
|
||||||
|
PointF canvasScale = sprite.getCanvasScale(getPreviewWidth(), getPreviewHeight());
|
||||||
|
PointF rotationAxis = sprite.getRotationAxis();
|
||||||
|
int rotation = sprite.getRotation();
|
||||||
|
|
||||||
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
canvas.translate(canvasPosition.x, canvasPosition.y);
|
||||||
|
canvas.scale(canvasScale.x, canvasScale.y);
|
||||||
|
canvas.rotate(rotation, rotationAxis.x, rotationAxis.y);
|
||||||
|
try {
|
||||||
|
view.draw(canvas);
|
||||||
|
if (status == Status.RENDER1) {
|
||||||
|
surface.unlockCanvasAndPost(canvas);
|
||||||
|
renderingStatus = Status.DONE1;
|
||||||
|
} else {
|
||||||
|
surface2.unlockCanvasAndPost(canvas);
|
||||||
|
renderingStatus = Status.DONE2;
|
||||||
|
}
|
||||||
|
//Sometimes draw could crash if you don't use main thread. Ensuring you can render always
|
||||||
|
} catch (Exception e) {
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
view.draw(canvas);
|
||||||
|
if (status == Status.RENDER1) {
|
||||||
|
surface.unlockCanvasAndPost(canvas);
|
||||||
|
renderingStatus = Status.DONE1;
|
||||||
|
} else {
|
||||||
|
surface2.unlockCanvasAndPost(canvas);
|
||||||
|
renderingStatus = Status.DONE2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopRender() {
|
||||||
|
running = false;
|
||||||
|
if (thread != null) {
|
||||||
|
thread.shutdownNow();
|
||||||
|
thread = null;
|
||||||
|
}
|
||||||
|
renderingStatus = Status.DONE1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -6,6 +22,7 @@ import android.opengl.Matrix;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import com.pedro.encoder.R;
|
import com.pedro.encoder.R;
|
||||||
|
import com.pedro.encoder.input.video.CameraHelper;
|
||||||
import com.pedro.encoder.utils.gl.GlUtil;
|
import com.pedro.encoder.utils.gl.GlUtil;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
@ -97,9 +114,34 @@ public class RotationFilterRender extends BaseFilterRender {
|
||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
//Set rotation
|
//Set rotation
|
||||||
Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f);
|
Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f);
|
||||||
|
Matrix.scaleM(rotationMatrix, 0, 1f, 1f, 0f);
|
||||||
//Translation
|
//Translation
|
||||||
//Matrix.translateM(rotationMatrix, 0, 0f, 0f, 0f);
|
//Matrix.translateM(rotationMatrix, 0, 0f, 0f, 0f);
|
||||||
// Combine the rotation matrix with the projection and camera view
|
// Combine the rotation matrix with the projection and camera view
|
||||||
Matrix.multiplyMM(MVPMatrix, 0, rotationMatrix, 0, MVPMatrix, 0);
|
Matrix.multiplyMM(MVPMatrix, 0, rotationMatrix, 0, MVPMatrix, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep aspect ratio if you rotate 90º or 270º.
|
||||||
|
* @param rotation value
|
||||||
|
* @param width width of stream (prepareVideo method) if you are streaming or preview (startPreview method) if you aren't streaming.
|
||||||
|
* @param height height of stream (prepareVideo method) if you are streaming or preview (startPreview method) if you aren't streaming.
|
||||||
|
*/
|
||||||
|
public void setRotationFixed(int rotation, int width, int height, boolean isPortrait) {
|
||||||
|
this.rotation = rotation;
|
||||||
|
//Set rotation
|
||||||
|
Matrix.setRotateM(rotationMatrix, 0, rotation, 0, 0, 1.0f);
|
||||||
|
if (rotation == 90 || rotation == 270) {
|
||||||
|
float value = (float) height / (float) width;
|
||||||
|
if (isPortrait) {
|
||||||
|
Matrix.scaleM(rotationMatrix, 0, value, 1f, 0f);
|
||||||
|
} else {
|
||||||
|
Matrix.scaleM(rotationMatrix, 0, 1f, value, 0f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Matrix.scaleM(rotationMatrix, 0, 1f, 1f, 0f);
|
||||||
|
}
|
||||||
|
// Combine the rotation matrix with the projection and camera view
|
||||||
|
Matrix.multiplyMM(MVPMatrix, 0, rotationMatrix, 0, MVPMatrix, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters;
|
package com.pedro.encoder.input.gl.render.filters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters.object;
|
package com.pedro.encoder.input.gl.render.filters.object;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters.object;
|
package com.pedro.encoder.input.gl.render.filters.object;
|
||||||
|
|
||||||
import android.opengl.GLES20;
|
import android.opengl.GLES20;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters.object;
|
package com.pedro.encoder.input.gl.render.filters.object;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters.object;
|
package com.pedro.encoder.input.gl.render.filters.object;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -8,6 +24,9 @@ import android.opengl.GLES20;
|
||||||
import android.opengl.Matrix;
|
import android.opengl.Matrix;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import com.pedro.encoder.R;
|
import com.pedro.encoder.R;
|
||||||
import com.pedro.encoder.input.gl.Sprite;
|
import com.pedro.encoder.input.gl.Sprite;
|
||||||
|
@ -25,7 +44,7 @@ import java.nio.FloatBuffer;
|
||||||
public class SurfaceFilterRender extends BaseObjectFilterRender {
|
public class SurfaceFilterRender extends BaseObjectFilterRender {
|
||||||
|
|
||||||
public interface SurfaceReadyCallback {
|
public interface SurfaceReadyCallback {
|
||||||
void surfaceReady();
|
void surfaceReady(SurfaceTexture surfaceTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
//rotation matrix
|
//rotation matrix
|
||||||
|
@ -92,11 +111,18 @@ public class SurfaceFilterRender extends BaseObjectFilterRender {
|
||||||
uSamplerSurfaceHandle = GLES20.glGetUniformLocation(program, "uSamplerSurface");
|
uSamplerSurfaceHandle = GLES20.glGetUniformLocation(program, "uSamplerSurface");
|
||||||
uAlphaHandle = GLES20.glGetUniformLocation(program, "uAlpha");
|
uAlphaHandle = GLES20.glGetUniformLocation(program, "uAlpha");
|
||||||
|
|
||||||
GlUtil.createExternalTextures(1, surfaceId, 0);
|
GlUtil.createExternalTextures(surfaceId.length, surfaceId, 0);
|
||||||
surfaceTexture = new SurfaceTexture(surfaceId[0]);
|
surfaceTexture = new SurfaceTexture(surfaceId[0]);
|
||||||
surfaceTexture.setDefaultBufferSize(getWidth(), getHeight());
|
surfaceTexture.setDefaultBufferSize(getWidth(), getHeight());
|
||||||
surface = new Surface(surfaceTexture);
|
surface = new Surface(surfaceTexture);
|
||||||
if (surfaceReadyCallback != null) surfaceReadyCallback.surfaceReady();
|
if (surfaceReadyCallback != null) {
|
||||||
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
surfaceReadyCallback.surfaceReady(surfaceTexture);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -142,10 +168,16 @@ public class SurfaceFilterRender extends BaseObjectFilterRender {
|
||||||
surface.release();
|
surface.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This texture must be renderer using an api called on main thread to avoid possible errors
|
||||||
|
*/
|
||||||
public SurfaceTexture getSurfaceTexture() {
|
public SurfaceTexture getSurfaceTexture() {
|
||||||
return surfaceTexture;
|
return surfaceTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This surface must be renderer using an api called on main thread to avoid possible errors
|
||||||
|
*/
|
||||||
public Surface getSurface() {
|
public Surface getSurface() {
|
||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.gl.render.filters.object;
|
package com.pedro.encoder.input.gl.render.filters.object;
|
||||||
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
@ -13,6 +29,11 @@ import com.pedro.encoder.utils.gl.TextStreamObject;
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
public class TextObjectFilterRender extends BaseObjectFilterRender {
|
public class TextObjectFilterRender extends BaseObjectFilterRender {
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
private float textSize;
|
||||||
|
private int textColor;
|
||||||
|
private Typeface typeface;
|
||||||
|
|
||||||
public TextObjectFilterRender() {
|
public TextObjectFilterRender() {
|
||||||
super();
|
super();
|
||||||
streamObject = new TextStreamObject();
|
streamObject = new TextStreamObject();
|
||||||
|
@ -31,7 +52,27 @@ public class TextObjectFilterRender extends BaseObjectFilterRender {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setText(String text, float textSize, int textColor, Typeface typeface) {
|
public void setText(String text, float textSize, int textColor, Typeface typeface) {
|
||||||
|
this.text = text;
|
||||||
|
this.textSize = textSize;
|
||||||
|
this.textColor = textColor;
|
||||||
|
this.typeface = typeface;
|
||||||
((TextStreamObject) streamObject).load(text, textSize, textColor, typeface);
|
((TextStreamObject) streamObject).load(text, textSize, textColor, typeface);
|
||||||
shouldLoad = true;
|
shouldLoad = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addText(String text) {
|
||||||
|
setText(this.text + text, textSize, textColor, typeface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateColor(int textColor) {
|
||||||
|
setText(this.text + text, textSize, textColor, typeface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTypeface(Typeface typeface) {
|
||||||
|
setText(this.text + text, textSize, textColor, typeface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTextSize(float textSize) {
|
||||||
|
setText(this.text + text, textSize, textColor, typeface);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.video;
|
package com.pedro.encoder.input.video;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.ImageFormat;
|
import android.graphics.ImageFormat;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
import android.media.CamcorderProfile;
|
import android.media.CamcorderProfile;
|
||||||
|
@ -10,9 +27,11 @@ import android.util.Log;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
import android.view.TextureView;
|
import android.view.TextureView;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import com.pedro.encoder.Frame;
|
import com.pedro.encoder.Frame;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -39,10 +58,11 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
private GetCameraData getCameraData;
|
private GetCameraData getCameraData;
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
private boolean lanternEnable = false;
|
private boolean lanternEnable = false;
|
||||||
|
private boolean videoStabilizationEnable = false;
|
||||||
|
private boolean autoFocusEnabled = false;
|
||||||
private int cameraSelect;
|
private int cameraSelect;
|
||||||
private boolean isFrontCamera = false;
|
private CameraHelper.Facing facing = CameraHelper.Facing.BACK;
|
||||||
private boolean isPortrait = false;
|
private boolean isPortrait = false;
|
||||||
private int cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
//default parameters for camera
|
//default parameters for camera
|
||||||
|
@ -55,12 +75,17 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
private List<Camera.Size> previewSizeBack;
|
private List<Camera.Size> previewSizeBack;
|
||||||
private List<Camera.Size> previewSizeFront;
|
private List<Camera.Size> previewSizeFront;
|
||||||
private float distance;
|
private float distance;
|
||||||
|
private CameraCallbacks cameraCallbacks;
|
||||||
|
private final int focusAreaSize = 100;
|
||||||
|
|
||||||
//Face detector
|
//Face detector
|
||||||
public interface FaceDetectorCallback {
|
public interface FaceDetectorCallback {
|
||||||
void onGetFaces(Camera.Face[] faces);
|
void onGetFaces(Camera.Face[] faces, Rect scaleSensor, int sensorOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final int sensorOrientation = 0;
|
||||||
|
//Value obtained from Camera.Face documentation api about bounds
|
||||||
|
private final Rect faceSensorScale = new Rect(-1000, -1000, 1000, 1000);
|
||||||
private FaceDetectorCallback faceDetectorCallback;
|
private FaceDetectorCallback faceDetectorCallback;
|
||||||
|
|
||||||
public Camera1ApiManager(SurfaceView surfaceView, GetCameraData getCameraData) {
|
public Camera1ApiManager(SurfaceView surfaceView, GetCameraData getCameraData) {
|
||||||
|
@ -106,23 +131,36 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCameraFacing(CameraHelper.Facing cameraFacing) {
|
||||||
|
facing = cameraFacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCameraSelect(int cameraFacing) {
|
||||||
|
cameraSelect = cameraFacing;
|
||||||
|
}
|
||||||
|
|
||||||
public void start(CameraHelper.Facing cameraFacing, int width, int height, int fps) {
|
public void start(CameraHelper.Facing cameraFacing, int width, int height, int fps) {
|
||||||
int facing = cameraFacing == CameraHelper.Facing.BACK ? Camera.CameraInfo.CAMERA_FACING_BACK
|
int facing = cameraFacing == CameraHelper.Facing.BACK ? Camera.CameraInfo.CAMERA_FACING_BACK
|
||||||
: Camera.CameraInfo.CAMERA_FACING_FRONT;
|
: Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.fps = fps;
|
this.fps = fps;
|
||||||
this.cameraFacing = facing;
|
|
||||||
cameraSelect =
|
cameraSelect =
|
||||||
facing == Camera.CameraInfo.CAMERA_FACING_BACK ? selectCameraBack() : selectCameraFront();
|
facing == Camera.CameraInfo.CAMERA_FACING_BACK ? selectCameraBack() : selectCameraFront();
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void start(int facing, int width, int height, int fps) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.fps = fps;
|
||||||
|
cameraSelect = facing;
|
||||||
|
selectCamera(facing);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
public void start(int width, int height, int fps) {
|
public void start(int width, int height, int fps) {
|
||||||
CameraHelper.Facing facing =
|
start(cameraSelect, width, height, fps);
|
||||||
cameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK ? CameraHelper.Facing.BACK
|
|
||||||
: CameraHelper.Facing.FRONT;
|
|
||||||
start(facing, width, height, fps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
|
@ -134,23 +172,27 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
camera = Camera.open(cameraSelect);
|
camera = Camera.open(cameraSelect);
|
||||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||||
Camera.getCameraInfo(cameraSelect, info);
|
Camera.getCameraInfo(cameraSelect, info);
|
||||||
isFrontCamera = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
|
facing = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK;
|
||||||
isPortrait = context.getResources().getConfiguration().orientation
|
isPortrait = context.getResources().getConfiguration().orientation
|
||||||
== Configuration.ORIENTATION_PORTRAIT;
|
== Configuration.ORIENTATION_PORTRAIT;
|
||||||
Camera.Parameters parameters = camera.getParameters();
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
parameters.setPreviewSize(width, height);
|
parameters.setPreviewSize(width, height);
|
||||||
parameters.setPreviewFormat(imageFormat);
|
parameters.setPreviewFormat(imageFormat);
|
||||||
int[] range = adaptFpsRange(fps, parameters.getSupportedPreviewFpsRange());
|
int[] range = adaptFpsRange(fps, parameters.getSupportedPreviewFpsRange());
|
||||||
|
Log.i(TAG, "fps: " + range[0] + " - " + range[1]);
|
||||||
parameters.setPreviewFpsRange(range[0], range[1]);
|
parameters.setPreviewFpsRange(range[0], range[1]);
|
||||||
|
|
||||||
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
||||||
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
|
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
|
||||||
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||||
|
autoFocusEnabled = true;
|
||||||
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
|
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
|
||||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
||||||
|
autoFocusEnabled = true;
|
||||||
} else {
|
} else {
|
||||||
parameters.setFocusMode(supportedFocusModes.get(0));
|
parameters.setFocusMode(supportedFocusModes.get(0));
|
||||||
|
autoFocusEnabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
camera.setParameters(parameters);
|
camera.setParameters(parameters);
|
||||||
|
@ -168,8 +210,13 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
camera.startPreview();
|
camera.startPreview();
|
||||||
running = true;
|
running = true;
|
||||||
|
if (cameraCallbacks != null) {
|
||||||
|
cameraCallbacks.onCameraOpened();
|
||||||
|
cameraCallbacks.onCameraChanged(facing);
|
||||||
|
}
|
||||||
Log.i(TAG, width + "X" + height);
|
Log.i(TAG, width + "X" + height);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
if (cameraCallbacks != null) cameraCallbacks.onCameraError(e.getMessage());
|
||||||
Log.e(TAG, "Error", e);
|
Log.e(TAG, "Error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +230,54 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setZoom(int level) {
|
||||||
|
try {
|
||||||
|
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
|
||||||
|
.isZoomSupported()) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
int maxZoom = params.getMaxZoom();
|
||||||
|
if (level > maxZoom) level = maxZoom;
|
||||||
|
else if (level < getMinZoom()) level = getMinZoom();
|
||||||
|
params.setZoom(level);
|
||||||
|
camera.setParameters(params);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZoom() {
|
||||||
|
try {
|
||||||
|
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
|
||||||
|
.isZoomSupported()) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
return params.getZoom();
|
||||||
|
} else {
|
||||||
|
return getMinZoom();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error", e);
|
||||||
|
return getMinZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxZoom() {
|
||||||
|
try {
|
||||||
|
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
|
||||||
|
.isZoomSupported()) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
return params.getMaxZoom();
|
||||||
|
} else {
|
||||||
|
return getMinZoom();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error", e);
|
||||||
|
return getMinZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinZoom() { return 0; }
|
||||||
|
|
||||||
public void setZoom(MotionEvent event) {
|
public void setZoom(MotionEvent event) {
|
||||||
try {
|
try {
|
||||||
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
|
if (camera != null && running && camera.getParameters() != null && camera.getParameters()
|
||||||
|
@ -211,6 +306,40 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExposure(int value) {
|
||||||
|
if (camera != null && camera.getParameters() != null) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
if (value > params.getMaxExposureCompensation()) value = params.getMaxExposureCompensation();
|
||||||
|
else if (value < params.getMinExposureCompensation()) value = params.getMinExposureCompensation();
|
||||||
|
params.setExposureCompensation(value);
|
||||||
|
camera.setParameters(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExposure() {
|
||||||
|
if (camera != null && camera.getParameters() != null) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
return params.getExposureCompensation();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxExposure() {
|
||||||
|
if (camera != null && camera.getParameters() != null) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
return params.getMaxExposureCompensation();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinExposure() {
|
||||||
|
if (camera != null && camera.getParameters() != null) {
|
||||||
|
android.hardware.Camera.Parameters params = camera.getParameters();
|
||||||
|
return params.getMinExposureCompensation();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private int selectCameraBack() {
|
private int selectCameraBack() {
|
||||||
return selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
|
return selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
|
||||||
}
|
}
|
||||||
|
@ -250,10 +379,15 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
|
int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
|
||||||
for (int[] range : fpsRanges) {
|
for (int[] range : fpsRanges) {
|
||||||
if (range[0] <= expectedFps && range[1] >= expectedFps) {
|
if (range[0] <= expectedFps && range[1] >= expectedFps) {
|
||||||
int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
|
int curMeasure = Math.abs(((range[0] + range[1]) / 2) - expectedFps);
|
||||||
if (curMeasure < measure) {
|
if (curMeasure < measure) {
|
||||||
closestRange = range;
|
closestRange = range;
|
||||||
measure = curMeasure;
|
measure = curMeasure;
|
||||||
|
} else if (curMeasure == measure) {
|
||||||
|
if (Math.abs(range[0] - expectedFps) < Math.abs(closestRange[1] - expectedFps)) {
|
||||||
|
closestRange = range;
|
||||||
|
measure = curMeasure;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,10 +396,22 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||||
getCameraData.inputYUVData(new Frame(data, rotation, isFrontCamera && isPortrait, imageFormat));
|
getCameraData.inputYUVData(new Frame(data, rotation, facing == CameraHelper.Facing.FRONT && isPortrait, imageFormat));
|
||||||
camera.addCallbackBuffer(yuvBuffer);
|
camera.addCallbackBuffer(yuvBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Camera.Size getCameraSize(int width, int height) {
|
||||||
|
if (camera != null) {
|
||||||
|
return camera.new Size(width, height);
|
||||||
|
} else {
|
||||||
|
camera = Camera.open(cameraSelect);
|
||||||
|
Camera.Size size = camera.new Size(width, height);
|
||||||
|
camera.release();
|
||||||
|
camera = null;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See: https://developer.android.com/reference/android/graphics/ImageFormat.html to know name of
|
* See: https://developer.android.com/reference/android/graphics/ImageFormat.html to know name of
|
||||||
* constant values
|
* constant values
|
||||||
|
@ -335,8 +481,8 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFrontCamera() {
|
public CameraHelper.Facing getCameraFacing() {
|
||||||
return isFrontCamera;
|
return facing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchCamera() throws CameraOpenException {
|
public void switchCamera() throws CameraOpenException {
|
||||||
|
@ -351,8 +497,6 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
throw new CameraOpenException("This camera resolution cant be opened");
|
throw new CameraOpenException("This camera resolution cant be opened");
|
||||||
}
|
}
|
||||||
stop();
|
stop();
|
||||||
cameraFacing = cameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK
|
|
||||||
? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
|
|
||||||
start();
|
start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -360,6 +504,20 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void switchCamera(int cameraId) throws CameraOpenException {
|
||||||
|
if (camera != null) {
|
||||||
|
int oldCamera = cameraSelect;
|
||||||
|
cameraSelect = cameraId;
|
||||||
|
if (!checkCanOpen()) {
|
||||||
|
cameraSelect = oldCamera;
|
||||||
|
throw new CameraOpenException("This camera resolution cant be opened");
|
||||||
|
}
|
||||||
|
stop();
|
||||||
|
start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean checkCanOpen() {
|
private boolean checkCanOpen() {
|
||||||
List<Camera.Size> previews;
|
List<Camera.Size> previews;
|
||||||
if (cameraSelect == selectCameraBack()) {
|
if (cameraSelect == selectCameraBack()) {
|
||||||
|
@ -399,6 +557,23 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<int[]> getSupportedFps() {
|
||||||
|
List<int[]> supportedFps;
|
||||||
|
if (camera != null) {
|
||||||
|
supportedFps = camera.getParameters().getSupportedPreviewFpsRange();
|
||||||
|
} else {
|
||||||
|
camera = Camera.open(cameraSelect);
|
||||||
|
supportedFps = camera.getParameters().getSupportedPreviewFpsRange();
|
||||||
|
camera.release();
|
||||||
|
camera = null;
|
||||||
|
}
|
||||||
|
for (int[] range : supportedFps) {
|
||||||
|
range[0] /= 1000;
|
||||||
|
range[1] /= 1000;
|
||||||
|
}
|
||||||
|
return supportedFps;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @required: <uses-permission android:name="android.permission.FLASHLIGHT"/>
|
* @required: <uses-permission android:name="android.permission.FLASHLIGHT"/>
|
||||||
*/
|
*/
|
||||||
|
@ -410,6 +585,76 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
lanternEnable = false;
|
lanternEnable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Camera.AutoFocusCallback autoFocusTakePictureCallback = new Camera.AutoFocusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAutoFocus(boolean success, Camera camera) {
|
||||||
|
if (success) {
|
||||||
|
Log.i(TAG, "tapToFocus success");
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "tapToFocus failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void tapToFocus(View view, MotionEvent event) {
|
||||||
|
if (camera != null && camera.getParameters() != null) {
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
if (parameters.getMaxNumMeteringAreas() > 0) {
|
||||||
|
Rect rect = calculateFocusArea(event.getX(), event.getY(), view.getWidth(), view.getHeight());
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
||||||
|
List<Camera.Area> meteringAreas = new ArrayList<>();
|
||||||
|
meteringAreas.add(new Camera.Area(rect, 800));
|
||||||
|
parameters.setFocusAreas(meteringAreas);
|
||||||
|
try {
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}catch (Exception e) {
|
||||||
|
Log.i(TAG, "tapToFocus error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
camera.autoFocus(autoFocusTakePictureCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableAutoFocus() {
|
||||||
|
if (camera != null) {
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
||||||
|
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
|
||||||
|
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||||
|
autoFocusEnabled = true;
|
||||||
|
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
|
||||||
|
autoFocusEnabled = true;
|
||||||
|
} else {
|
||||||
|
autoFocusEnabled = false;
|
||||||
|
parameters.setFocusMode(supportedFocusModes.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableAutoFocus() {
|
||||||
|
if (camera != null) {
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
||||||
|
if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) {
|
||||||
|
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
|
||||||
|
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
|
||||||
|
} else {
|
||||||
|
parameters.setFocusMode(supportedFocusModes.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
autoFocusEnabled = false;
|
||||||
|
camera.setParameters(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoFocusEnabled() {
|
||||||
|
return autoFocusEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public void enableRecordingHint() {
|
public void enableRecordingHint() {
|
||||||
if (camera != null) {
|
if (camera != null) {
|
||||||
|
@ -427,11 +672,20 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableFaceDetection(FaceDetectorCallback faceDetectorCallback) {
|
public boolean enableFaceDetection(FaceDetectorCallback faceDetectorCallback) {
|
||||||
if (camera != null) {
|
try {
|
||||||
this.faceDetectorCallback = faceDetectorCallback;
|
if (camera != null) {
|
||||||
camera.setFaceDetectionListener(this);
|
this.faceDetectorCallback = faceDetectorCallback;
|
||||||
camera.startFaceDetection();
|
camera.setFaceDetectionListener(this);
|
||||||
|
camera.startFaceDetection();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "face detection called with camera stopped");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.e(TAG, "face detection unsupported");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,12 +697,61 @@ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean enableVideoStabilization() {
|
||||||
|
if (camera != null) {
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
if (parameters.isVideoStabilizationSupported()) {
|
||||||
|
parameters.setVideoStabilization(true);
|
||||||
|
videoStabilizationEnable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return videoStabilizationEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableVideoStabilization() {
|
||||||
|
if (camera != null) {
|
||||||
|
Camera.Parameters parameters = camera.getParameters();
|
||||||
|
if (parameters.isVideoStabilizationSupported()) {
|
||||||
|
parameters.setVideoStabilization(false);
|
||||||
|
videoStabilizationEnable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVideoStabilizationEnabled() {
|
||||||
|
return videoStabilizationEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCameraCallbacks(CameraCallbacks cameraCallbacks) {
|
||||||
|
this.cameraCallbacks = cameraCallbacks;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFaceDetectionEnabled() {
|
public boolean isFaceDetectionEnabled() {
|
||||||
return faceDetectorCallback != null;
|
return faceDetectorCallback != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
|
public void onFaceDetection(Camera.Face[] faces, Camera camera) {
|
||||||
if (faceDetectorCallback != null) faceDetectorCallback.onGetFaces(faces);
|
if (faceDetectorCallback != null) faceDetectorCallback.onGetFaces(faces, faceSensorScale, sensorOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rect calculateFocusArea(float x, float y, float previewWidth, float previewHeight) {
|
||||||
|
int left = clamp((int) (x / previewWidth * 2000f - 1000f), focusAreaSize);
|
||||||
|
int top = clamp((int) (y / previewHeight * 2000f - 1000f), focusAreaSize);
|
||||||
|
return new Rect(left, top, left + focusAreaSize, top + focusAreaSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int clamp(int touchCoordinateInCameraReper, int focusAreaSize) {
|
||||||
|
int result;
|
||||||
|
if (Math.abs(touchCoordinateInCameraReper) + focusAreaSize / 2 > 1000){
|
||||||
|
if (touchCoordinateInCameraReper > 0){
|
||||||
|
result = 1000 - focusAreaSize / 2;
|
||||||
|
} else {
|
||||||
|
result = -1000 + focusAreaSize / 2;
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
result = touchCoordinateInCameraReper - focusAreaSize / 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||||
|
}
|
|
@ -1,10 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.video;
|
package com.pedro.encoder.input.video;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Range;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by pedro on 17/12/18.
|
* Created by pedro on 17/12/18.
|
||||||
*/
|
*/
|
||||||
|
@ -54,6 +74,20 @@ public class CameraHelper {
|
||||||
return (float) Math.sqrt(x * x + y * y);
|
return (float) Math.sqrt(x * x + y * y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to fix camera2 quality related with fps range.
|
||||||
|
* Add a device if needed.
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public static boolean discardCamera2Fps(Range<Integer> range, Facing facing) {
|
||||||
|
//On Google pixel 4a, 30 and 15 fps ranges produce quality problems with camera2 using facing back.
|
||||||
|
//Build.MODEL.equalsIgnoreCase("Pixel 4") maybe necessary in future.
|
||||||
|
if (Build.MODEL.equalsIgnoreCase("Pixel 4a")) {
|
||||||
|
return facing == Facing.BACK && (range.getUpper() == 30 || range.getUpper() == 15);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Facing {
|
public enum Facing {
|
||||||
BACK, FRONT
|
BACK, FRONT
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2021 pedroSG94.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.pedro.encoder.input.video;
|
package com.pedro.encoder.input.video;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue