Custom I/O via avio_alloc_context and multiple callbacks

1,202 views
Skip to first unread message

Marc Giger

unread,
Dec 20, 2013, 3:05:57 PM12/20/13
to javacpp...@googlegroups.com
Hi,

I'm using the javacpp ffmpeg presets and have implemented custom I/O via the avio_alloc_context which works fine so far.
Now I need multiple I/O streams and therefore also independent callback handlers for each stream.
I'm aware of the limitation that just one callback handler can be used at the same time and also
read in this forum that so called C++ functors may be used to circumvent this limitation.

Just to see what happens I annotated the Read_packet_Pointer_BytePointer_int with the @ByRef annotation:

public static native AVIOContext avio_alloc_context(
                  @Cast("unsigned char*") BytePointer buffer,
                  int buffer_size,
                  int write_flag,
                  Pointer opaque,
                  @ByRef Read_packet_Pointer_BytePointer_int read_packet,
                  Write_packet_Pointer_BytePointer_int write_packet,
                  Seek_Pointer_long_int seek);

This results in a compilation error:

/home/giger/projects/giger/jamus/native/target/linux-x86_64/javacpp.presets-src/ffmpeg/target/classes/com/googlecode/javacpp/jniavformat.cpp: In function ‘_jobject* Java_com_googlecode_javacpp_avformat_avio_1alloc_1context(JNIEnv*, jclass, jobject, jint, jint, jobject, jobject, jobject, jobject)’:
/home/giger/projects/giger/jamus/native/target/linux-x86_64/javacpp.presets-src/ffmpeg/target/classes/com/googlecode/javacpp/jniavformat.cpp:10981:150: error: cannot convert ‘JavaCPP_com_googlecode_javacpp_avformat_00024Read_1packet_1Pointer_1BytePointer_1int’ to ‘int (*)(void*, uint8_t*, int) {aka int (*)(void*, unsigned char*, int)}’ for argument ‘5’ to ‘AVIOContext* avio_alloc_context(unsigned char*, int, int, void*, int (*)(void*, uint8_t*, int), int (*)(void*, uint8_t*, int), int64_t (*)(void*, int64_t, int))’

Then I compared the code with the generated code from the sort sample provided by Samuel (thanks!) to see what the differences are. In principle
it is exactly the same logic for both cases so it could be a simple casting issue which I am unable to solve since my C / C++ knowledge is very limited.

Apart from that I'm not sure if that would work anyway. Samuel mentioned something about "functors based on operator()" (first time I heard anything about that;-) )
(https://groups.google.com/forum/#!topic/javacpp-project/6rXYBqn8iGA) and I'm not sure if that is given in this case.
Do I need to implement such a functor and possibly wrap the call to avio_alloc_context() in it somehow?

I would appreciate any help!

Many thanks,

Marc

Samuel Audet

unread,
Dec 20, 2013, 7:02:51 PM12/20/13
to javacpp...@googlegroups.com
Hello,

On 12/21/2013 05:05 AM, Marc Giger wrote:
> I'm using the javacpp ffmpeg presets and have implemented custom I/O via the avio_alloc_context which works fine so far.
> Now I need multiple I/O streams and therefore also independent callback handlers for each stream.
> I'm aware of the limitation that just one callback handler can be used at the same time and also
> read in this forum that so called C++ functors may be used to circumvent this limitation.

Yes, that works for C++, but FFmpeg is C, so that's not going to work.
The way to work around that at the moment is by declaring multiple
allocate() methods, and use a different one for each of the instances of
the FunctionPointer, for example:

public static class Read_packet_Pointer_BytePointer_int extends
FunctionPointer {
static { Loader.load(); }
protected native void allocate();
protected native void allocate2();
protected native void allocate3();
protected native void allocate4();
protected native void allocate5();
protected native void allocate6();
// etc
public native int call(Pointer opaque, @Cast("uint8_t*")
BytePointer buf, int buf_size);
}


And then in your code (I think those could still be anonymous classes):

public static class FirstStreamRead extends
Read_packet_Pointer_BytePointer_int {
FirstStreamRead() { allocate(); }
public int call(Pointer opaque, BytePointer buf, int buf_size) {
return 0;
}
}
public static class SecondStreamRead extends
Read_packet_Pointer_BytePointer_int {
SecondStreamRead() { allocate2(); }
public int call(Pointer opaque, BytePointer buf, int buf_size) {
return 0;
}
}
// etc

The Parser doesn't generate those, for now, but I guess it's something
we could add...

> Apart from that I'm not sure if that would work anyway. Samuel mentioned
> something about "functors based on operator()" (first time I heard anything about that;-) )
> (https://groups.google.com/forum/#!topic/javacpp-project/6rXYBqn8iGA)

Actually, I think the correct name for that is "function object", as
mentioned here for example:
http://www.cplusplus.com/reference/functional/

Samuel

Marc Giger

unread,
Dec 21, 2013, 3:49:42 AM12/21/13
to javacpp...@googlegroups.com
Hi Samuel,

Thanks for your answer!

I already did declare multiple allocate() methods as you showed below. My problem
is that I don't know beforehand how much streams will be used. Additionally the
code is called from multiple threads which is my main issue.

I know that functors can't be used in C but what is if I implement a functor
in C++ that somehow wraps the call to the avio_alloc_context() function and
then use this functor instead of calling avio_alloc_context() directly?
Do you think something like that would work?

Many thanks,

Marc

Samuel Audet

unread,
Dec 21, 2013, 4:24:37 AM12/21/13
to javacpp...@googlegroups.com
Hi,

I see. C++ won't help us much, no. We can do the same kind of thing we
need to do, but entirely in Java. Anyway, in C, callbacks always
(usually) allow he user to provide a data pointer for that purpose. Now,
we can't directly store a Java reference in there unless we add some
additional JNI code for that, but we can use it to store integers, that
can mapped to Java references. Something like this should work (but not
tested):

Pointer[] pointers = new Pointer[MAX_STREAMS];
for (int i = 0; i < MAX_STREAMS; i++) {
final int n = i;
pointers[i] = new Pointer() { { address = n; } };
}
HashMap<Pointer, Stream> streamMap = new HashMap<Pointer, Stream>();

And for the n'th stream where !streamMap.containsKey(pointers[n]):

streamMap.put(pointers[n], streams[n]);
Read_packet_Pointer_BytePointer_int callback = new
Read_packet_Pointer_BytePointer_int() {
public int call(Pointer opaque, BytePointer buf, int buf_size) {
streamMap.get(opaque).process(buf.capacity(buf_size));
}
};
context[n] = avio_alloc_context(..., pointers[n], callback, ...);

Access to HashMap can be synchronized for multithread safety...

Samuel

Marc Giger

unread,
Dec 21, 2013, 7:04:26 AM12/21/13
to javacpp...@googlegroups.com
Hi Samuel,

It works! (with small modifications):

Pointer tmpPointer = new Pointer() {
    {
        address = callbackAddress.incrementAndGet(); //AtomicLong
        //todo address must be > 0 !
    }
};
//create a new (non-anonymous) Pointer so that
//"else if (obj.getClass() != getClass()) {
// in the equals() method returns the same class-object
Pointer callbackPointer = new Pointer(tmpPointer);
streamCallbackkMap.put(callbackPointer, inputStream);

Now I will look if a WeakHashMap may be used or find another good approach to have
the HashMap cleaned up after the work is done.

Many many thanks for your help Samuel!

Marc

Samuel Audet

unread,
Dec 21, 2013, 9:20:29 AM12/21/13
to javacpp...@googlegroups.com
Great!

Just a couple of notes. It may be possible to simply use an array as
well, and that would not use the equals() method. Moreover, it's
possible to declare the FunctionPointer with a '@Cast("void*") long
opaque' parameter instead, so we wouldn't need to hack around with
Pointer, but there is no hook in Parser at the moment to modify the
produced files in such fine details.

Well, in any case, if you figure out some things that you feel should be
changed in Pointer, let me know! Thanks

Samuel

On 12/21/2013 09:04 PM, Marc Giger wrote:
> Hi Samuel,
>
Reply all
Reply to author
Forward
0 new messages