javaproject-panama

Java 22 Foreign Function & Memory API: traversing structs


I'm trying to use the Foreign Function & Memory API in Java 22 to call into FFmpeg's libavformat. First, I used jextract to generate a binding:

jextract --target-package org.ffmpeg.avformat \
    --output /path/to/src/main/java \
    -I <base include dir> libavformat/avformat.h

My initial goal is to open a video file and get its dimensions. Many FFmpeg functions make use of a struct called AVFormatContext, which is a main context struct that refers to other structs, which refer to other structs, etc. So I need to walk through a few different structs to get the width & height:

struct AVFormatContext {
    unsigned int nb_streams;
    AVStream **streams;
    // etc.
}

struct AVStream {
    AVCodecParameters *codecpar;
    // etc.
}

struct AVCodecParameters {
    int width;
    int height;
    // etc.
}

In C, this would involve something like:

int width  = formatCtx->streams[i]->codecpar->width;
int height = formatCtx->streams[i]->codecpar->height;

Here is my attempt in Java:

try (Arena arena = Arena.ofConfined()) {
    ///////////////////// begin setup ////////////////////////

    MemorySegment formatCtxPtr  = arena.allocate(C_POINTER);
    MemorySegment inputFormat   = MemorySegment.NULL;
    MemorySegment formatDictPtr = MemorySegment.NULL;

    MemorySegment url = arena.allocateFrom("file:/path/to/video.mp4");

    int result = avformat_open_input(formatCtxPtr, url, inputFormat, formatDictPtr);
    if (result != 0) {
        throw new IOException("Couldn't open file");
    }

    MemorySegment formatCtx = formatCtxPtr.get(C_POINTER, 0);

    // Verify the AVFormatContext
    av_dump_format(formatCtx, 0, url, 0);

    ///////////////////// end setup ////////////////////////

    StructLayout formatCtxLayout = (StructLayout) AVFormatContext.layout();

    VarHandle streamsHandle  = formatCtxLayout.varHandle(
            MemoryLayout.PathElement.groupElement("streams"));
    MemorySegment streamsPtr = (MemorySegment) streamsHandle.get(formatCtx, 0);
    MemorySegment stream     = streamsPtr.getAtIndex(C_POINTER, 0);

    StructLayout avStreamLayout  = (StructLayout) AVStream.layout();
    VarHandle codecParamsHandle  = avStreamLayout.varHandle(
            MemoryLayout.PathElement.groupElement("codecpar"));
    MemorySegment codecParamsPtr = (MemorySegment) codecParamsHandle.get(stream, 0);
    MemorySegment codecParams    = codecParamsPtr.get(C_POINTER, 0);

    StructLayout codecParamsLayout = (StructLayout) AVCodecParameters.layout();
    VarHandle widthHandle          = codecParamsLayout.varHandle(
            MemoryLayout.PathElement.groupElement("width"));
    int width                      = (int) widthHandle.get(codecParams, 0);
}

Sadly that last call to widthHandle.get() causes: java.lang.InternalError: a fault occurred in an unsafe memory access operation


Solution

  • It looks like you have one too many dereferences:

    MemorySegment codecParams    = codecParamsPtr.get(C_POINTER, 0);
    

    The codecParamsPtr is a AVCodecParameters*, which you then try to load a pointer from. That will however just read the first 8 bytes of the struct as a pointer.

    Also, like @DuncG says in the comments, you can just use the getters that jextract generates. This should work:

    // AVStream**
    MemorySegment streams = AVFormatContext.streams(formatCtx);
    // AVStream*
    MemorySegment stream = streams.getAtIndex(C_POINTER, 0);
    
    // AVCodecParameters*
    MemorySegment codecParams = AVStream.codecpar(stream);
    
    int width = AVCodecParameters.width(codecParams);