3

I have a native function which signature is like -

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

I need to pass this pData from C++ to a Java method public void OnrecvData(byte[] data) without copying the data. Currently I am doing this by allocating jByteBuffer and using SetByteArrayRegion. But I think this approach doesn't avoid copy. I am looking for an approach like the other way GetDirectBufferAddress and pass a pointer to a starting address and length to avoid copy.

Thanks in advance!

EDIT 1

I won't modify the data in Java layer. Read-only access will be okay.

EDIT 2

You can dequeueInputBuffer(), pass that buffer into native code, and memcpy() the data. I'm 99% sure the MediaCodec buffers are direct ByteBuffers, so you can get the address and length using JNI functions. How you call dequeueInputBuffer() is up to you. You have to use the buffers returned by MediaCodec, so one copy is unavoidable, but at least it's the encoded data. (For the output side, you want to use a Surface, for multiple reasons.)

My current code is like this -

    // ..................
    // ..................
    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);
        }
    }

Here encodedData is byte[] which is being received from native layer from unsigned char* using one time memory allocation and doing SetByteArrayRegion every time. So I am requiring one copy in my current implementation like memcpy way you suggested. Both these approach require one copy, but is my current implementation is less efficient than your suggested one (sending dequeueInputBuffer address reference and memcpy)? Like puting byte[] onto ByteBuffer I did in Java Layer?

EDIT 3

Well, ByteBuffer.put(byte[], offset, offsetLength) seems like copying whole byte[] array into ByteBuffer like memcpy. So its another copy in Java layer also. I am going to implement your idea now. Thanks :)

Kaidul
  • 15,409
  • 15
  • 81
  • 150

1 Answers1

4

The question is slightly more complicated than it seems.

The fastest way to make the data visible in Java-language code is to pre-allocate a "direct" ByteBuffer, and receive the data there. The data is immediately accessible from native or managed code. However, "accessible" just means that the Java-language code can get at it, not that it can get at individual bytes quickly.

If you allocate the direct ByteBuffer from JNI (using NewDirectByteBuffer), you can pass it an arbitrary buffer, but that means there can't be a byte[] backing the storage. If you allocate it from managed code using ByteBuffer#allocateDirect(), recent versions of Android will give you a direct buffer that lives on the managed heap, which means it is also accessible through array(). With that, code written in Java can just access it like any other byte[], rather than having to call a method for every access.

If your data is arriving in buffers you don't control (e.g. video decoder output), then you don't really have a choice. You either have to copy the data into a managed byte[], or pay a per-byte overhead on the Java side. (In theory the JIT can recognize what you're doing and optimize it, but I don't know if this is being done.)

Do try to avoid allocating buffers. Pre-allocation and pooling can help your performance.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • Thanks for your answer! In my case, I am passing encoded data from C++ to Java layer for decoding using `MediaCodec`. So I have no control on the buffer I think and as a result, I have no chance to avoid copy (as you said). So should I go for using calling `MediaCodec` functions using JNI? Which will be more efficient? - 1) Copying 1280 x 720 video buffer from C++ to Java and do the decoding in Java layer to avoid JNI calls 2) avoid the copy and perform JNI calls – Kaidul Oct 23 '15 at 07:00
  • Though I am not sure if I could avoid copy if I choose JNI calls – Kaidul Oct 23 '15 at 07:31
  • You can `dequeueInputBuffer()`, pass that buffer into native code, and `memcpy()` the data. I'm 99% sure the MediaCodec buffers are direct ByteBuffers, so you can get the address and length using JNI functions. How you call `dequeueInputBuffer()` is up to you. You have to use the buffers returned by MediaCodec, so one copy is unavoidable, but at least it's the encoded data. (For the output side, you want to use a Surface, for multiple reasons.) – fadden Oct 23 '15 at 16:06
  • Thanks for your suggestion. Isn't it possible to send the `ByteBuffer` returned by `getInputBuffer(mMediaCodec.dequeueInputBuffer(mTimeoutUs))` and in native layer instead of `memcpy`, change the starting address and length in this way: http://stackoverflow.com/a/16511876/1162233 ? – Kaidul Oct 26 '15 at 01:51
  • 1
    It might work, it might not. My expectation is that the MediaCodec native code is allocating buffers and placing pointers into the ByteBuffers. The MediaCodec implementation might read the pointers back out of the ByteBuffer later, but more likely it will assume that when you release buffer N you're referring to the memory it allocated. So you can try it, but I'd be concerned about its portability across releases of Android even if it does work. – fadden Oct 26 '15 at 03:40
  • Yes I think so. So I may be try it later but for now, I am going to implement your idea. So the gist is - send the `ByteBuffer` as `jobject` in jni layer and get starting pointer using `GetDirectBufferAddress` and then `memcpy` the `unsigned char*` data. Then the data will be available in Java on that `ByteBuffer` object. So there is no `byte[]` stuffs in this approach, right? Please make me correct if I am wrong. – Kaidul Oct 26 '15 at 05:27