我有一个本机函数,签名就像 -
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);
}
}
此处encodedData
是byte[]
,它是unsigned char*
从本地层接收的,使用一次内存分配并每次都执行SetByteArrayRegion
。因此,我需要在我当前的实现中使用一个副本,例如您建议的memcpy
方式。这两种方法都需要一个副本,但我目前的实现效率低于建议的方式(发送dequeueInputBuffer
地址引用和memcpy
)?就像put
byte[]
到ByteBuffer
我在Java Layer中做过的那样?
编辑3
好吧,ByteBuffer.put(byte[], offset, offsetLength)
似乎将整个byte[]
数组复制到ByteBuffer
memcpy
。所以它在Java层中也是另一个副本。我现在要实现你的想法。谢谢:))
答案 0 :(得分:3)
问题稍微复杂一些。
在Java语言代码中使数据可见的最快方法是预先分配“直接”ByteBuffer,并在那里接收数据。可以从本机代码或托管代码立即访问数据。但是,“可访问”只意味着Java语言代码可以获得它,而不是它可以快速获得单个字节 。
如果从JNI分配直接ByteBuffer(使用NewDirectByteBuffer
),则可以将其传递给任意缓冲区,但这意味着不能有byte[]
支持存储。如果您使用ByteBuffer#allocateDirect()
从托管代码分配它,则最新版本的Android将为您提供一个位于托管堆上的直接缓冲区,这意味着它也可以通过array()
访问。这样,用Java编写的代码就可以像任何其他byte[]
一样访问它,而不必为每次访问调用一个方法。
如果您的数据到达缓冲区而您无法控制(例如视频解码器输出),那么您实际上没有选择。您必须将数据复制到托管byte[]
,或者在Java端支付每字节开销。 (从理论上讲,JIT可以识别你正在做的事情并对其进行优化,但我不知道这是否已经完成。)
尽量避免分配缓冲区。预分配和汇集可以帮助您提高绩效。