Я пытаюсь использовать API внешних функций и памяти в Java 22 для вызова формата libavformat FFmpeg. Сначала я использовал jextract для создания привязки:
jextract --target-package org.ffmpeg.avformat \
--output /path/to/src/main/java \
-I <base include dir> libavformat/avformat.h
Моя первоначальная цель — открыть видеофайл и узнать его размеры. Многие функции FFmpeg используют структуру AVFormatContext, которая является структурой основного контекста, которая ссылается на другие структуры, которые ссылаются на другие структуры и т. д. Поэтому мне нужно пройтись по нескольким различным структурам, чтобы получить ширину и высоту:
struct AVFormatContext {
unsigned int nb_streams;
AVStream **streams;
// etc.
}
struct AVStream {
AVCodecParameters *codecpar;
// etc.
}
struct AVCodecParameters {
int width;
int height;
// etc.
}
В C это будет включать что-то вроде:
int width = formatCtx->streams[i]->codecpar->width;
int height = formatCtx->streams[i]->codecpar->height;
Вот моя попытка на 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);
}
К сожалению, последний вызов widthHandle.get()
вызывает: java.lang.InternalError: a fault occurred in an unsafe memory access operation
Похоже, у вас слишком много разыменований:
MemorySegment codecParams = codecParamsPtr.get(C_POINTER, 0);
codecParamsPtr
— это AVCodecParameters*
, из которого вы затем пытаетесь загрузить указатель. Однако при этом будут прочитаны только первые 8 байтов структуры как указатель.
Кроме того, как говорит @DuncG в комментариях, вы можете просто использовать геттеры, которые генерирует jextract. Это должно работать:
// 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);
Работает как по волшебству! Спасибо!
Я не знаком с libavformat, но jextract должен был генерировать геттерные (и индексированные) вызовы геттеров для получения каждого поля-члена каждой из этих структур, избегая необходимости использовать Layout/VarHandle. например, посмотри, есть ли у тебя
AVFormatContext.streams(formatCtx)
и т. д.