mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-21 16:32:39 +00:00
implement enough to launch 2.2 (#2)
* generic maintenance and allow armv8 * implement missing java functions * Update build.yml * add missing audiodevice code * split apk support * ignore (gitignore) * replace library load with native code * set supported abis again --------- Co-authored-by: alk <45172705+altalk23@users.noreply.github.com>
This commit is contained in:
1 parent
c08ae0ec13
commit
c85470013f
24 files changed
+522
-252
No files matched your search
@@ -0,0 +1,66 @@
|
||||
package org.fmod
|
||||
|
||||
import android.media.AudioTrack
|
||||
import android.util.Log
|
||||
|
||||
class AudioDevice {
|
||||
private var mTrack: AudioTrack? = null
|
||||
private fun fetchChannelConfigFromCount(i: Int): Int {
|
||||
if (i == 1) {
|
||||
return 2
|
||||
}
|
||||
if (i == 2) {
|
||||
return 3
|
||||
}
|
||||
if (i == 6) {
|
||||
return 252
|
||||
}
|
||||
return if (i == 8) 6396 else 0
|
||||
}
|
||||
|
||||
fun init(i: Int, i2: Int, i3: Int, i4: Int): Boolean {
|
||||
val fetchChannelConfigFromCount = fetchChannelConfigFromCount(i)
|
||||
val minBufferSize = AudioTrack.getMinBufferSize(i2, fetchChannelConfigFromCount, 2)
|
||||
if (minBufferSize < 0) {
|
||||
Log.w(
|
||||
"fmod",
|
||||
"AudioDevice::init : Couldn't query minimum buffer size, possibly unsupported sample rate or channel count"
|
||||
)
|
||||
} else {
|
||||
Log.i("fmod", "AudioDevice::init : Min buffer size: $minBufferSize bytes")
|
||||
}
|
||||
val i5 = i3 * i4 * i * 2
|
||||
val i6 = if (i5 > minBufferSize) i5 else minBufferSize
|
||||
Log.i("fmod", "AudioDevice::init : Actual buffer size: $i6 bytes")
|
||||
return try {
|
||||
val audioTrack = AudioTrack(3, i2, fetchChannelConfigFromCount, 2, i6, 1)
|
||||
mTrack = audioTrack
|
||||
try {
|
||||
audioTrack.play()
|
||||
true
|
||||
} catch (unused: IllegalStateException) {
|
||||
Log.e("fmod", "AudioDevice::init : AudioTrack play caused IllegalStateException")
|
||||
mTrack?.release()
|
||||
mTrack = null
|
||||
false
|
||||
}
|
||||
} catch (unused2: IllegalArgumentException) {
|
||||
Log.e("fmod", "AudioDevice::init : AudioTrack creation caused IllegalArgumentException")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
try {
|
||||
mTrack?.stop()
|
||||
} catch (unused: IllegalStateException) {
|
||||
Log.e("fmod", "AudioDevice::init : AudioTrack stop caused IllegalStateException")
|
||||
}
|
||||
mTrack?.release()
|
||||
mTrack = null
|
||||
}
|
||||
|
||||
fun write(sArr: ShortArray?, i: Int) {
|
||||
mTrack!!.write(sArr!!, 0, i)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.fmod
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.AssetManager
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
// i never agreed to your licenses! take that
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object FMOD {
|
||||
private var gContext: Context? = null
|
||||
private val gPluginBroadcastReceiver = PluginBroadcastReceiver()
|
||||
|
||||
external fun OutputAAudioHeadphonesChanged()
|
||||
|
||||
@JvmStatic
|
||||
fun init(context: Context?) {
|
||||
gContext = context
|
||||
if (context != null) {
|
||||
gContext!!.registerReceiver(
|
||||
gPluginBroadcastReceiver,
|
||||
IntentFilter("android.intent.action.HEADSET_PLUG")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun close() {
|
||||
val context = gContext
|
||||
context?.unregisterReceiver(gPluginBroadcastReceiver)
|
||||
gContext = null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun checkInit(): Boolean {
|
||||
return gContext != null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getAssetManager(): AssetManager? {
|
||||
val context = gContext
|
||||
return context?.assets
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun supportsLowLatency(): Boolean {
|
||||
val outputBlockSize = getOutputBlockSize()
|
||||
val lowLatencyFlag = lowLatencyFlag()
|
||||
val proAudioFlag = proAudioFlag()
|
||||
val z = outputBlockSize in 1..1024
|
||||
val isBluetoothOn = isBluetoothOn()
|
||||
Log.i(
|
||||
"fmod",
|
||||
"FMOD::supportsLowLatency : Low latency = $lowLatencyFlag, Pro Audio = $proAudioFlag, Bluetooth On = $isBluetoothOn, Acceptable Block Size = $z ($outputBlockSize)"
|
||||
)
|
||||
return z && lowLatencyFlag && !isBluetoothOn
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun lowLatencyFlag(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 5) {
|
||||
return false
|
||||
}
|
||||
|
||||
val context = gContext ?: return false
|
||||
return context.packageManager.hasSystemFeature("android.hardware.audio.low_latency")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun proAudioFlag(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < 5) {
|
||||
return false
|
||||
}
|
||||
|
||||
val context = gContext ?: return false
|
||||
return context.packageManager.hasSystemFeature("android.hardware.audio.pro")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun supportsAAudio(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= 27
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOutputSampleRate(): Int {
|
||||
if (Build.VERSION.SDK_INT < 17) {
|
||||
return 0
|
||||
}
|
||||
|
||||
val context = gContext ?: return 0
|
||||
val audioService = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
val property = audioService.getProperty("android.media.property.OUTPUT_SAMPLE_RATE")
|
||||
|
||||
return property?.toInt() ?: 0
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOutputBlockSize(): Int {
|
||||
if (Build.VERSION.SDK_INT < 17) {
|
||||
return 0
|
||||
}
|
||||
|
||||
val context = gContext ?: return 0
|
||||
val audioService = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
val property = audioService.getProperty("android.media.property.OUTPUT_FRAMES_PER_BUFFER")
|
||||
|
||||
return property?.toInt() ?: 0
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isBluetoothOn(): Boolean {
|
||||
val context = gContext ?: return false
|
||||
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
return audioManager.isBluetoothA2dpOn || audioManager.isBluetoothScoOn
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fileDescriptorFromUri(str: String?): Int {
|
||||
gContext?.apply {
|
||||
try {
|
||||
contentResolver.openFileDescriptor(Uri.parse(str), "r")?.apply {
|
||||
return detachFd()
|
||||
}
|
||||
} catch (_: FileNotFoundException) { }
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
internal class PluginBroadcastReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
OutputAAudioHeadphonesChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package org.fmod
|
||||
|
||||
import android.media.MediaCrypto
|
||||
import android.media.MediaDataSource
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaCodec.BufferInfo
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class MediaCodec {
|
||||
private var channelCount = 0
|
||||
|
||||
var mCodecPtr: Long = 0
|
||||
private var mCurrentOutputBufferIndex = -1
|
||||
private var mDataSourceProxy: Any? = null
|
||||
private var mDecoder: android.media.MediaCodec? = null
|
||||
private var mExtractor: MediaExtractor? = null
|
||||
private var mInputBuffers: Array<ByteBuffer>? = null
|
||||
private var mInputFinished = false
|
||||
private var length: Long = 0
|
||||
private var mOutputBuffers: Array<ByteBuffer>? = null
|
||||
private var mOutputFinished = false
|
||||
private var sampleRate = 0
|
||||
|
||||
fun init(j: Long): Boolean {
|
||||
mCodecPtr = j
|
||||
var i = 0
|
||||
|
||||
try {
|
||||
val mediaExtractor = MediaExtractor()
|
||||
mExtractor = mediaExtractor
|
||||
mediaExtractor.setDataSource(object : MediaDataSource() {
|
||||
override fun close() {}
|
||||
override fun readAt(j: Long, bArr: ByteArray, i: Int, i2: Int): Int {
|
||||
return fmodReadAt(mCodecPtr, j, bArr, i, i2)
|
||||
}
|
||||
|
||||
override fun getSize(): Long {
|
||||
return fmodGetSize(mCodecPtr)
|
||||
}
|
||||
})
|
||||
} catch (e5: IOException) {
|
||||
Log.w("fmod", "MediaCodec::init : $e5")
|
||||
return false
|
||||
}
|
||||
|
||||
val trackCount = mExtractor!!.trackCount
|
||||
var i2 = 0
|
||||
while (i2 < trackCount) {
|
||||
val trackFormat = mExtractor!!.getTrackFormat(i2)
|
||||
val string = trackFormat.getString("mime")
|
||||
Log.d(
|
||||
"fmod",
|
||||
"MediaCodec::init : Format $i2 / $trackCount -- $trackFormat"
|
||||
)
|
||||
if (string == "audio/mp4a-latm") {
|
||||
return try {
|
||||
mDecoder = android.media.MediaCodec.createDecoderByType(string)
|
||||
mExtractor!!.selectTrack(i2)
|
||||
mDecoder!!.configure(trackFormat, null as Surface?, null as MediaCrypto?, 0)
|
||||
mDecoder!!.start()
|
||||
mInputBuffers = mDecoder!!.inputBuffers
|
||||
mOutputBuffers = mDecoder!!.outputBuffers
|
||||
val integer =
|
||||
if (trackFormat.containsKey("encoder-delay")) trackFormat.getInteger("encoder-delay") else 0
|
||||
if (trackFormat.containsKey("encoder-padding")) {
|
||||
i = trackFormat.getInteger("encoder-padding")
|
||||
}
|
||||
val j2 = trackFormat.getLong("durationUs")
|
||||
channelCount = trackFormat.getInteger("channel-count")
|
||||
val integer2 = trackFormat.getInteger("sample-rate")
|
||||
sampleRate = integer2
|
||||
length =
|
||||
(((j2 * integer2.toLong() + 999999) / 1000000).toInt() - integer - i).toLong()
|
||||
true
|
||||
} catch (e6: IOException) {
|
||||
Log.e("fmod", "MediaCodec::init : $e6")
|
||||
false
|
||||
}
|
||||
} else {
|
||||
i2++
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun release() {
|
||||
val mediaCodec = mDecoder
|
||||
if (mediaCodec != null) {
|
||||
mediaCodec.stop()
|
||||
mDecoder?.release()
|
||||
mDecoder = null
|
||||
}
|
||||
val mediaExtractor = mExtractor
|
||||
if (mediaExtractor != null) {
|
||||
mediaExtractor.release()
|
||||
mExtractor = null
|
||||
}
|
||||
}
|
||||
|
||||
fun read(bArr: ByteArray?, i: Int): Int {
|
||||
var dequeueInputBuffer = 0
|
||||
val i2 =
|
||||
if (!mInputFinished || !mOutputFinished || mCurrentOutputBufferIndex != -1) 0 else -1
|
||||
while (!mInputFinished && mDecoder!!.dequeueInputBuffer(0).also {
|
||||
dequeueInputBuffer = it
|
||||
} >= 0) {
|
||||
val readSampleData = mExtractor!!.readSampleData(mInputBuffers!![dequeueInputBuffer], 0)
|
||||
if (readSampleData >= 0) {
|
||||
mDecoder!!.queueInputBuffer(
|
||||
dequeueInputBuffer,
|
||||
0,
|
||||
readSampleData,
|
||||
mExtractor!!.sampleTime,
|
||||
0
|
||||
)
|
||||
mExtractor!!.advance()
|
||||
} else {
|
||||
mDecoder!!.queueInputBuffer(dequeueInputBuffer, 0, 0, 0, 4)
|
||||
mInputFinished = true
|
||||
}
|
||||
}
|
||||
if (!mOutputFinished && mCurrentOutputBufferIndex == -1) {
|
||||
val bufferInfo = BufferInfo()
|
||||
val dequeueOutputBuffer = mDecoder!!.dequeueOutputBuffer(bufferInfo, 10000)
|
||||
if (dequeueOutputBuffer >= 0) {
|
||||
mCurrentOutputBufferIndex = dequeueOutputBuffer
|
||||
mOutputBuffers!![dequeueOutputBuffer].limit(bufferInfo.size)
|
||||
mOutputBuffers!![dequeueOutputBuffer].position(bufferInfo.offset)
|
||||
} else if (dequeueOutputBuffer == -3) {
|
||||
mOutputBuffers = mDecoder!!.outputBuffers
|
||||
} else if (dequeueOutputBuffer == -2) {
|
||||
Log.d(
|
||||
"fmod",
|
||||
"MediaCodec::read : MediaCodec::dequeueOutputBuffer returned MediaCodec.INFO_OUTPUT_FORMAT_CHANGED " + mDecoder!!.outputFormat
|
||||
)
|
||||
} else if (dequeueOutputBuffer == -1) {
|
||||
Log.d(
|
||||
"fmod",
|
||||
"MediaCodec::read : MediaCodec::dequeueOutputBuffer returned MediaCodec.INFO_TRY_AGAIN_LATER."
|
||||
)
|
||||
} else {
|
||||
Log.w(
|
||||
"fmod",
|
||||
"MediaCodec::read : MediaCodec::dequeueOutputBuffer returned $dequeueOutputBuffer"
|
||||
)
|
||||
}
|
||||
if (bufferInfo.flags and 4 !== 0) {
|
||||
mOutputFinished = true
|
||||
}
|
||||
}
|
||||
val i3 = mCurrentOutputBufferIndex
|
||||
if (i3 == -1) {
|
||||
return i2
|
||||
}
|
||||
val byteBuffer = mOutputBuffers!![i3]
|
||||
val min = byteBuffer.remaining().coerceAtMost(i)
|
||||
byteBuffer[bArr, 0, min]
|
||||
if (!byteBuffer.hasRemaining()) {
|
||||
byteBuffer.clear()
|
||||
mDecoder!!.releaseOutputBuffer(mCurrentOutputBufferIndex, false)
|
||||
mCurrentOutputBufferIndex = -1
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
fun seek(i: Int) {
|
||||
val i2 = mCurrentOutputBufferIndex
|
||||
if (i2 != -1) {
|
||||
mOutputBuffers!![i2].clear()
|
||||
mCurrentOutputBufferIndex = -1
|
||||
}
|
||||
mInputFinished = false
|
||||
mOutputFinished = false
|
||||
mDecoder!!.flush()
|
||||
val j = i.toLong()
|
||||
mExtractor!!.seekTo(j * 1000000 / sampleRate.toLong(), MediaExtractor.SEEK_TO_PREVIOUS_SYNC)
|
||||
val sampleTime = (mExtractor!!.sampleTime * sampleRate.toLong() + 999999) / 1000000
|
||||
var i3 = ((j - sampleTime) * channelCount.toLong() * 2).toInt()
|
||||
if (i3 < 0) {
|
||||
Log.w(
|
||||
"fmod",
|
||||
"MediaCodec::seek : Seek to $i resulted in position $sampleTime"
|
||||
)
|
||||
return
|
||||
}
|
||||
val bArr = ByteArray(1024)
|
||||
while (i3 > 0) {
|
||||
i3 -= read(bArr, Math.min(1024, i3))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
external fun fmodGetSize(j: Long): Long
|
||||
|
||||
external fun fmodReadAt(j: Long, j2: Long, bArr: ByteArray?, i: Int, i2: Int): Int
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user