微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 ByteBuffer

如何解决使用 ByteBuffer

我正在编写一个 Android 应用程序,其中本机线程以大约 60 fps 的速度不断生成图像帧(作为原始像素数据),然后应该显示SurfaceView 中。目前,线程将像素数据写入原生线程和JVM共享的直接ByteBuffer,然后通过JNI调用回调通知JVM端一帧准备就绪;然后将缓冲区读入 Bitmap 并在 SurfaceView 上绘制。我的代码大致如下(完整的源代码太大而无法共享):

// this is a call into native code that retrieves
// the width and height of the frame in pixels
// returns something on the order of 320×200
private external fun getFrameDimensions(): (Int,Int)

// a direct ByteBuffer shared between the JVM and the native thread
private val frameBuffer: ByteBuffer

private fun getFrame(): Bitmap {
    val (width,height) = getFrameDimensions()

    return Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888).apply {
        setHasAlpha(false)
        copyPixelsFromBuffer(frameBuffer.apply {
            order(ByteOrder.nativeOrder())
            rewind()
        })
    }
}

// the SurfaceView widget which should display the image
private lateinit var surfaceView: SurfaceView

private val SCALE = 8

// callback invoked by the native thread
public fun onFrameReady() {
    val canvas: Canvas = surfaceView.holder.lockCanvas() ?: return
    val bitmap = getFrame()
    
    try {
        canvas.drawBitmap(bitmap.scale(
            bitmap.width * SCALE,bitmap.height * SCALE),0.0f,null
        )
    } finally {
        holder.unlockCanvasAndPost(canvas)
    }
}

好消息是上述方法有效:屏幕以大致预期的帧速率更新。但是,性能确实很差,以至于应用程序锁定:它停止响应例如按 按钮,最终会弹出 ANR 消息。显然我做错了什么,但我不太能说出究竟是什么。

有没有办法让上面的运行速度更快?我最好避免编写不必要的本机代码。我特别想避免将任何特定于 Android 的东西放在事物的本机方面。

解决方法

嗯,有几个问题。 Kotlin 代码的主要问题是您每秒大约创建一个位图 60 次,并且不使用该方法来处理它 bitmap.recycle(),这会在您不再需要该位图的本机内存后释放它。虽然帧速率为 60 fps,但很难找到正确的时间释放它,但我认为它应该在执行帧绘制之后的某个地方。

但是您的整体代码中的主要问题是在本机代码中存在使用表面视图的正确方法,而您没有使用它。要正确执行此操作,您必须将 Surface 从 Android 代码直接传递到本机代码。之后,您应该通过 ANativeWindow_fromSurface() 获得 ANativeWindow。使用 ANativeWindow_setBuffersGeometry() 设置大小和颜色格式。锁定缓冲区,复制像素并解锁缓冲区以发布它。全部在本机代码中,无需任何进一步的 Android 交互。它看起来像

ANativeWindow* window = ANativeWindow_fromSurface(env,javaSurface);
ANativeWindow_setBuffersGeometry(window,width,height,WINDOW_FORMAT_RGBA_8888);

ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window,&buffer,NULL) == 0) {
  memcpy(buffer.bits,pixels,width * height * 2);
  ANativeWindow_unlockAndPost(window);
}

ANativeWindow_release(window);

请注意,通过这种方式,您不会创建一大堆位图,这是导致性能问题的主要原因 - 您将直接将像素写入 Surface,而无需任何中间容器 网上有很多关于此的信息,甚至 NDK 示例中的谷歌 sample 也显示了类似的方法(它仍然通过编解码器呈现视频)。如果有什么不清楚的,请 ping 我,我会尽力为您提供更多详细信息。

您可以使用的另一种方法是使用 Android 提供的 OpenGl ES API 以高效的方式在 SurfaceView 上绘制图像 - 还有大量在线信息。不过,与原生绘图相比,它的效率会更低。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。