You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 22外部函数与内存API遍历结构体:调用FFmpeg获视频尺寸报错

使用Java 22 Foreign Function & Memory API调用FFmpeg获取视频分辨率时的内存访问错误

背景与目标

我尝试用Java 22的Foreign Function & Memory API调用FFmpeg的libavformat库,先通过jextract生成绑定:

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

目标是打开视频文件并获取分辨率,FFmpeg中需要遍历AVFormatContext→AVStream→AVCodecParameters结构体来获取宽高,相关逻辑的C语言实现如下:

相关C结构体定义

struct AVFormatContext {
    unsigned int nb_streams;
    AVStream **streams;
    // 其他字段
}

struct AVStream {
    AVCodecParameters *codecpar;
    // 其他字段
}

struct AVCodecParameters {
    int width;
    int height;
    // 其他字段
}

C语言获取宽高的代码

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

Java实现尝试

我编写的Java代码如下:

try (Arena arena = Arena.ofConfined()) {
    ///////////////////// 初始化部分 ////////////////////////

    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("无法打开文件");
    }

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

    // 验证AVFormatContext
    av_dump_format(formatCtx, 0, url, 0);

    ///////////////////// 初始化结束 ////////////////////////

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

遇到的错误

执行到widthHandle.get()时触发错误:

java.lang.InternalError: 不安全内存访问操作中发生错误

问题分析与解决方法

1. 缺失流信息解析步骤

仅调用avformat_open_input无法填充流的完整参数,必须后续调用avformat_find_stream_info解析流的详细信息,否则codecpar可能未被正确初始化:

// 在av_dump_format之前添加以下代码
result = avformat_find_stream_info(formatCtx, formatDictPtr);
if (result < 0) {
    throw new IOException("无法获取流信息");
}

2. 未做空指针与流类型检查

FFmpeg的streams数组可能包含无效流,codecpar也可能为空,且并非所有流都是视频流。必须添加检查逻辑:

// 读取流数量
VarHandle nbStreamsHandle = formatCtxLayout.varHandle(MemoryLayout.PathElement.groupElement("nb_streams"));
int nbStreams = (int) nbStreamsHandle.get(formatCtx, 0);

StructLayout codecParamsLayout = (StructLayout) AVCodecParameters.layout();
VarHandle codecTypeHandle = codecParamsLayout.varHandle(MemoryLayout.PathElement.groupElement("codec_type"));
VarHandle widthHandle = codecParamsLayout.varHandle(MemoryLayout.PathElement.groupElement("width"));
VarHandle heightHandle = codecParamsLayout.varHandle(MemoryLayout.PathElement.groupElement("height"));

for (int i = 0; i < nbStreams; i++) {
    MemorySegment stream = streamsPtr.getAtIndex(C_POINTER, i);
    if (stream == MemorySegment.NULL) continue;

    MemorySegment codecParams = (MemorySegment) codecParamsHandle.get(stream, 0);
    if (codecParams == MemorySegment.NULL) continue;

    // 筛选视频流(AVMEDIA_TYPE_VIDEO为jextract生成的常量)
    if ((int) codecTypeHandle.get(codecParams, 0) == AVMEDIA_TYPE_VIDEO()) {
        int width = (int) widthHandle.get(codecParams, 0);
        int height = (int) heightHandle.get(codecParams, 0);
        System.out.printf("视频分辨率:%dx%d%n", width, height);
        break;
    }
}

3. 结构体布局可能不匹配

FFmpeg结构体存在平台相关的内存填充,若jextract使用的头文件与链接的FFmpeg库版本不一致,会导致内存偏移错误:

  • 确保jextract使用的头文件和运行时链接的FFmpeg库版本完全一致
  • 可通过C语言的offsetof宏验证结构体成员偏移,对比jextract生成的StructLayout中的偏移值

4. 资源泄漏风险

使用完FFmpeg上下文后,必须调用avformat_close_input释放资源:

finally {
    if (!formatCtxPtr.isNull()) {
        avformat_close_input(formatCtxPtr);
    }
}

内容的提问来源于stack exchange,提问作者alexantd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:24:57