I am trying to integrate AR core with Agora. I am totally newbie to OpenGL concepts.
what I have till now? I am using this sample AR Kotlin in my application.
everything working fine with AR core I have this class for sending custom video to Agora
package com.ex.ab.arcore.kotlin.common.helpers
import android.app.ActivityManager
import android.content.Context
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import android.util.Log
import timber.log.Timber
import java.nio.ByteBuffer
import java.nio.ByteOrder
object GLSurfaceViewToI420Converter {
private const val TAG = "GLSurfaceToI420"
private const val BYTES_PER_PIXEL = Float.SIZE_BYTES // Assuming RGBA format
private const val YUV_BYTES_PER_PIXEL = 3 // I420 format
fun isAvailable(context: Context): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val configurationInfo = activityManager.deviceConfigurationInfo
return configurationInfo.reqGlEsVersion >= 0x30000
}
fun convertGLSurfaceViewToByteArray(width: Int, height: Int): ByteArray? {
Timber.d("CHECK width = $width height = $height")
// val width = glSurfaceView.width
// val height = glSurfaceView.height
val rgbaBuffer = ByteBuffer.allocateDirect(width * height * 4)
rgbaBuffer.order(ByteOrder.nativeOrder())
// Read pixel data from the framebuffer
GLES30.glReadPixels(
0,
0,
width,
height,
GLES30.GL_RGBA,
GLES30.GL_UNSIGNED_BYTE,
rgbaBuffer
)
// Convert to byte array
val byteArray = ByteArray(rgbaBuffer.remaining())
rgbaBuffer[byteArray]
return byteArray
}
fun convertGLSurfaceViewToI420(width: Int, height: Int): ByteBuffer? {
val framebuffer = IntArray(1)
GLES30.glGenFramebuffers(1, framebuffer, 0)
if (framebuffer[0] == 0) {
Log.e(TAG, "CHECK Failed to generate framebuffer")
return null
}
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, framebuffer[0])
val texture = IntArray(1)
GLES30.glGenTextures(1, texture, 0)
if (texture[0] == 0) {
Log.e(TAG, "CHECK Failed to generate texture")
GLES30.glDeleteFramebuffers(1, framebuffer, 0)
return null
}
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture[0])
GLES30.glTexImage2D(
GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0,
GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null
)
GLES30.glFramebufferTexture2D(
GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D, texture[0], 0
)
if (GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE) {
Log.e(TAG, "CHECK Framebuffer is not complete")
GLES30.glDeleteTextures(1, texture, 0)
GLES30.glDeleteFramebuffers(1, framebuffer, 0)
return null
}
val buffer = ByteBuffer.allocateDirect(width * height * YUV_BYTES_PER_PIXEL)
buffer.order(ByteOrder.nativeOrder())
val rgbaBuffer = ByteBuffer.allocateDirect(width * height * BYTES_PER_PIXEL)
rgbaBuffer.order(ByteOrder.nativeOrder())
GLES30.glReadPixels(0, 0, width, height, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, rgbaBuffer)
convertRGBAToI420(rgbaBuffer, buffer, width, height)
GLES30.glDeleteTextures(1, texture, 0)
GLES30.glDeleteFramebuffers(1, framebuffer, 0)
return buffer
}
private fun convertRGBAToI420(rgbaBuffer: ByteBuffer, i420Buffer: ByteBuffer, width: Int, height: Int) {
val ySize = width * height
val uvSize = ySize / 4
for (i in 0 until height) {
for (j in 0 until width) {
val rgbaIndex = (i * width + j) * BYTES_PER_PIXEL
val yIndex = i * width + j
val uvIndex = ySize + (i / 2) * (width / 2) + (j / 2) * 2
val r = rgbaBuffer.get(rgbaIndex).toInt() and 0xFF
val g = rgbaBuffer.get(rgbaIndex + 1).toInt() and 0xFF
val b = rgbaBuffer.get(rgbaIndex + 2).toInt() and 0xFF
val y = ((66 * r + 129 * g + 25 * b + 128) shr 8) + 16
val u = ((-38 * r - 74 * g + 112 * b + 128) shr 8) + 128
val v = ((112 * r - 94 * g - 18 * b + 128) shr 8) + 128
i420Buffer.put(yIndex, y.toByte())
i420Buffer.put(uvIndex, u.toByte())
i420Buffer.put(uvIndex + 1, v.toByte())
}
}
}
}
in OnDrawFrame() if I use convertGLSurfaceViewToI420
this method it gives Log.e(TAG, "CHECK Failed to generate framebuffer")
this error if I use convertGLSurfaceViewToByteArray
this method I only see green screen on other end.
my code to send buffer looks something like
activity.lifecycleScope.launch(Dispatchers.Main) {
val width = activity.localSurfaceView.width
val height = activity.localSurfaceView.height
// withContext(Dispatchers.IO) {
val isAvailable = GLSurfaceViewToI420Converter.isAvailable(activity.requireActivity())
Timber.d("CHECK isavailable $isAvailable")
val i420Buffer: ByteBuffer? =
GLSurfaceViewToI420Converter.convertGLSurfaceViewToI420(width, height)
i420Buffer?.let { data ->
val byteArray = ByteArray(data.remaining())
data.get(byteArray)
// byteArray?.let {
onByteArrayAvailable?.onByteDataAvailable(byteArray, width, height)
} ?: kotlin.run {
Timber.d("CHECK i420 is null")
}
// }
}
I am not getting any error of exceptions. have also checked GLError. If someone can point me to the solution it'll be great.