【问题标题】:Most efficient way to pass unsigned char* from C++ to java as byte[]将 unsigned char* 作为字节 [] 从 C++ 传递到 java 的最有效方法
【发布时间】:2015-10-23 02:59:51
【问题描述】:

我有一个原生函数,签名就像 -

void onRecvData(const unsigned char* pData, int length)

我需要将这个 pData 从 C++ 传递到 Java 方法 public void OnrecvData(byte[] data) 而不复制数据。目前我通过分配jByteBuffer 和使用SetByteArrayRegion 来做到这一点。但我认为这种方法并不能避免复制。我正在寻找与 GetDirectBufferAddress 类似的方法,并将指针传递给起始地址和长度以避免复制。

提前致谢!

编辑 1

我不会修改 Java 层的数据。只读访问就可以了。

编辑 2

您可以 dequeueInputBuffer(),将该缓冲区传递给本机代码,然后 memcpy() 数据。我 99% 确定 MediaCodec 缓冲区是直接的 ByteBuffers,因此您可以使用 JNI 获取地址和长度 职能。如何调用 dequeueInputBuffer() 取决于您。你必须 使用 MediaCodec 返回的缓冲区,所以一份副本是不可避免的, 但至少它是编码数据。 (对于输出端,您想要 出于多种原因,请使用 Surface。)

我现在的代码是这样的——

    // ..................
    // ..................
    int inIndex = mMediaCodec.dequeueInputBuffer(mTimeoutUs);
    if (inIndex >= 0) {
        ByteBuffer buffer;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            buffer = mMediaCodec.getInputBuffers()[inIndex];
            buffer.clear();
        } else {
            buffer = mMediaCodec.getInputBuffer(inIndex);
        }
        if (buffer != null) {
            buffer.put(encodedData, 0, encodedDataLength);
            long presentationTimeUs = System.nanoTime() / 1000l;
            mMediaCodec.queueInputBuffer(inIndex, 0, encodedDataLength, presentationTimeUs, 0);
        }
    }

这里的encodedDatabyte[],它是从本地层从unsigned char* 接收的,使用一次内存分配并每次都执行SetByteArrayRegion。因此,我在当前的实现中需要一份副本,例如您建议的memcpy。这两种方法都需要一份副本,但我目前的实现是否比您建议的效率低(发送dequeueInputBuffer 地址参考和memcpy)?就像 puting byte[]ByteBuffer 我在 Java 层做的?

编辑 3

好吧,ByteBuffer.put(byte[], offset, offsetLength) 似乎将整个 byte[] 数组复制到 ByteBuffer 中,例如 memcpy。所以它也是Java层的另一个副本。我现在要实施你的想法。谢谢:)

【问题讨论】:

  • 虽然您标记了 JNI,但最好指定您使用的是 JNI 以避免在此处混淆。

标签: java android c++ android-ndk java-native-interface


【解决方案1】:

这个问题比看起来要复杂一些。

使数据在 Java 语言代码中可见的最快方法是预先分配一个“直接”ByteBuffer,并在那里接收数据。可以从本机或托管代码立即访问数据。但是,“可访问”只是意味着 Java 语言代码可以访问它,而不是它可以快速访问单个字节。

如果您从 JNI 分配直接 ByteBuffer(使用 NewDirectByteBuffer),您可以将任意缓冲区传递给它,但这意味着不能有 byte[] 支持存储。如果您使用ByteBuffer#allocateDirect() 从托管代码中分配它,最新版本的Android 将为您提供一个位于托管堆上的直接缓冲区,这意味着它也可以通过array() 访问。这样,用 Java 编写的代码就可以像任何其他 byte[] 一样访问它,而不必为每次访问调用一个方法。

如果您的数据到达您无法控制的缓冲区(例如视频解码器输出),那么您真的别无选择。您要么必须将数据复制到托管的byte[],要么在 Java 端支付每字节的开销。 (理论上JIT可以识别你在做什么并优化它,但我不知道这是否正在做。)

请尽量避免分配缓冲区。预分配和池化可以帮助您提高性能。

【讨论】:

  • 感谢您的回答!在我的例子中,我将编码数据从 C++ 传递到 Java 层以使用 MediaCodec 进行解码。所以我无法控制我认为的缓冲区,因此,我没有机会避免复制(如你所说)。那么我应该使用 JNI 调用 MediaCodec 函数吗?哪个会更有效率? - 1) 将 1280 x 720 视频缓冲区从 C++ 复制到 Java 并在 Java 层进行解码以避免 JNI 调用 2) 避免复制并执行 JNI 调用
  • 虽然我不确定如果我选择 JNI 调用是否可以避免复制
  • 您可以dequeueInputBuffer(),将该缓冲区传递到本机代码,然后memcpy() 数据。我 99% 确定 MediaCodec 缓冲区是直接的 ByteBuffers,因此您可以使用 JNI 函数获取地址和长度。如何称呼dequeueInputBuffer() 取决于您。您必须使用 MediaCodec 返回的缓冲区,所以一份副本是不可避免的,但至少它是编码数据。 (对于输出端,出于多种原因,您想使用 Surface。)
  • 感谢您的建议。是不是可以发送getInputBuffer(mMediaCodec.dequeueInputBuffer(mTimeoutUs))返回的ByteBuffer在native层而不是memcpy,这样改起始地址和长度:stackoverflow.com/a/16511876/1162233
  • 它可能有效,也可能无效。我的期望是 MediaCodec 本机代码正在分配缓冲区并将指针放入 ByteBuffers。 MediaCodec 实现可能稍后会从 ByteBuffer 中读回指针,但更有可能的是,它会假设当您释放缓冲区 N 时,您指的是它分配的内存。所以你可以尝试一下,但我会担心它在 Android 版本之间的可移植性,即使它确实有效。
猜你喜欢
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
  • 2015-03-19
  • 2014-07-27
  • 1970-01-01
  • 1970-01-01
  • 2011-09-08
  • 1970-01-01
相关资源
最近更新 更多