FFMPEG 和 JNI - 将 AVFrame 数据传递给 Java 并返回
FFMPEG and JNI - pass AVFrame data to Java and Back
我有一些 C 代码可以逐帧解码视频。我到了 BGR32 中有一个 AVFrame 的地步,想将它发送回 Java 进行编辑。
我的 C 代码中有一个 ByteBuffer 对象,它是在 Java 中使用 AllocateDirect 创建的,但我很难写入 AVFrame->data[0 的内容] (属于 uint8_t 类型)并读回。我试过 memcpy 但没有运气。有谁知道如何实现这一目标?
更新
按照下面的 Will 评论并在 C
中写了这个
char *buf = (*pEnv)->GetDirectBufferAddress(pEnv, byteBuffer);
memcpy(buf, rgb_frame->data[0], output_width*output_height*4);
缓冲区确实包含 Java 中的一些数据,但执行以下 returns 空位图
BufferedImage frame = ImageIO.read(bitmapStream);
其中 bitmapStream 是此处定义的 ByteBufferInputStream:
https://code.google.com/p/kryo/source/browse/trunk/src/com/esotericsoftware/kryo/io/ByteBufferInputStream.java?r=205
不确定我是否没有在这个缓冲区中正确写入内容
更新 2
多亏了最新的代码片段,现在已经很接近了。我在我的 C 代码中使用 BGR32 格式,即每个像素 4 个字节。所以我在 Java:
中做了一些修改
final byte[] dataArray = new byte[width*height*4];
imageData.get(dataArray);
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
final DataBuffer buffer = new DataBufferByte(dataArray, dataArray.length);
Raster raster = Raster.createRaster(sampleModel, buffer, null);
image.setData(raster);
我得到的图像是正确的,但颜色通道似乎有问题
尝试了不同的格式,但没有成功
来自 Oracle 的 JNI 函数文档
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetDirectBufferAddress
GetDirectBufferAddress
void* GetDirectBufferAddress(JNIEnv* env, jobject buf);
Fetches and returns the starting address of the memory region
referenced by the given direct java.nio.Buffer.
This function allows native code to access the same memory region that
is accessible to Java code via the buffer object. LINKAGE:
Index 230 in the JNIEnv interface function table. PARAMETERS:
env: the JNIEnv interface pointer
buf: a direct java.nio.Buffer object (must not be NULL) RETURNS:
Returns the starting address of the memory region referenced by the
buffer. Returns NULL if the memory region is undefined, if the given
object is not a direct java.nio.Buffer, or if JNI access to direct
buffers is not supported by this virtual machine. SINCE:
JDK/JRE 1.4
我用这个 C++ 代码测试过:
static char framebuf[100];
JNIEXPORT void JNICALL Java_javaapplication45_UseByteBuffer_readBuf
(JNIEnv *env, jobject usebb, jobject bb) {
void *addr = env->GetDirectBufferAddress(bb);
framebuf[0] = 77;
memcpy(addr,framebuf,100);
}
和这个Java代码:
public class UseByteBuffer {
public native void readBuf(ByteBuffer bb);
}
...
public static void main(String[] args) {
System.load("/home/shackle/NetBeansProjects/usebb/dist/Debug/GNU-Linux-x86/libusebb.so");
ByteBuffer bb = ByteBuffer.allocateDirect(100);
new UseByteBuffer().readBuf(bb);
byte first_byte = bb.get(0);
System.out.println("first_byte = " + first_byte);
}
它打印了 first_byte=77 表明它正确复制了数据。
更新
ImageIO.read() 不会接受任何字节集,它必须采用已安装的 ImageReader 之一可以识别的格式,例如 JPEG 或 PNG。
这是一个将(3 字节 r,g,b )字节的原始数据转化为图像的示例
int width = 256;
int height = 256;
ByteBuffer bb = ByteBuffer.allocateDirect(height*width*3);
byte[] raw = new byte[width * height * 3];
bb.get(raw);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
DataBuffer buffer = new DataBufferByte(raw, raw.length);
SampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 3, width * 3, new int[]{0,1,2});
Raster raster = Raster.createRaster(sampleModel, buffer, null);
image.setData(raster);
更新 2
对于 BGR32,我相信这会更接近:
ByteBuffer imageData = ByteBuffer.allocateDirect(height * width * 4);
byte[] raw = new byte[width * height * 4];
imageData.get(raw);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
DataBuffer buffer = new DataBufferByte(raw, raw.length);
SampleModel sampleModel = new ComponentSampleModel(
DataBuffer.TYPE_BYTE, width, height, 4, width * 4,
new int[]{2,1,0} // Try {1,2,3}, {3,2,1}, {0,1,2}
);
Raster raster = Raster.createRaster(sampleModel, buffer, null);
image.setData(raster);
注意我评论的地方,我怀疑您可能需要在 ComponentSampleModel 构造函数的第三个参数中试验 bandOffsets 数组来修复颜色模型。
更新 3
可以通过对 WritableRaster 使用 BufferedImage.copyData() 而不是使用 getRaster() 来重用 sampleModel 从图像中获取数据。
SampleModel sampleModel = new ComponentSampleModel(
DataBuffer.TYPE_BYTE, width, height, 4, width * 4,
new int[]{2, 1, 0}
);
...
BufferedImage newImage = ImageIO.read(new File("test.png"));
byte newRaw[] = new byte[height*width*4];
DataBuffer newBuffer = new DataBufferByte(newRaw, newRaw.length);
WritableRaster newRaster = Raster.createWritableRaster(sampleModel, newBuffer, null);
newImage.copyData(newRaster);
我有一些 C 代码可以逐帧解码视频。我到了 BGR32 中有一个 AVFrame 的地步,想将它发送回 Java 进行编辑。
我的 C 代码中有一个 ByteBuffer 对象,它是在 Java 中使用 AllocateDirect 创建的,但我很难写入 AVFrame->data[0 的内容] (属于 uint8_t 类型)并读回。我试过 memcpy 但没有运气。有谁知道如何实现这一目标?
更新 按照下面的 Will 评论并在 C
中写了这个char *buf = (*pEnv)->GetDirectBufferAddress(pEnv, byteBuffer);
memcpy(buf, rgb_frame->data[0], output_width*output_height*4);
缓冲区确实包含 Java 中的一些数据,但执行以下 returns 空位图
BufferedImage frame = ImageIO.read(bitmapStream);
其中 bitmapStream 是此处定义的 ByteBufferInputStream: https://code.google.com/p/kryo/source/browse/trunk/src/com/esotericsoftware/kryo/io/ByteBufferInputStream.java?r=205
不确定我是否没有在这个缓冲区中正确写入内容
更新 2
多亏了最新的代码片段,现在已经很接近了。我在我的 C 代码中使用 BGR32 格式,即每个像素 4 个字节。所以我在 Java:
中做了一些修改final byte[] dataArray = new byte[width*height*4];
imageData.get(dataArray);
final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
final DataBuffer buffer = new DataBufferByte(dataArray, dataArray.length);
Raster raster = Raster.createRaster(sampleModel, buffer, null);
image.setData(raster);
我得到的图像是正确的,但颜色通道似乎有问题
尝试了不同的格式,但没有成功
来自 Oracle 的 JNI 函数文档
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetDirectBufferAddress
GetDirectBufferAddress
void* GetDirectBufferAddress(JNIEnv* env, jobject buf);
Fetches and returns the starting address of the memory region referenced by the given direct java.nio.Buffer.
This function allows native code to access the same memory region that is accessible to Java code via the buffer object. LINKAGE:
Index 230 in the JNIEnv interface function table. PARAMETERS:
env: the JNIEnv interface pointer
buf: a direct java.nio.Buffer object (must not be NULL) RETURNS:
Returns the starting address of the memory region referenced by the buffer. Returns NULL if the memory region is undefined, if the given object is not a direct java.nio.Buffer, or if JNI access to direct buffers is not supported by this virtual machine. SINCE:
JDK/JRE 1.4
我用这个 C++ 代码测试过:
static char framebuf[100];
JNIEXPORT void JNICALL Java_javaapplication45_UseByteBuffer_readBuf
(JNIEnv *env, jobject usebb, jobject bb) {
void *addr = env->GetDirectBufferAddress(bb);
framebuf[0] = 77;
memcpy(addr,framebuf,100);
}
和这个Java代码:
public class UseByteBuffer {
public native void readBuf(ByteBuffer bb);
}
...
public static void main(String[] args) {
System.load("/home/shackle/NetBeansProjects/usebb/dist/Debug/GNU-Linux-x86/libusebb.so");
ByteBuffer bb = ByteBuffer.allocateDirect(100);
new UseByteBuffer().readBuf(bb);
byte first_byte = bb.get(0);
System.out.println("first_byte = " + first_byte);
}
它打印了 first_byte=77 表明它正确复制了数据。
更新
ImageIO.read() 不会接受任何字节集,它必须采用已安装的 ImageReader 之一可以识别的格式,例如 JPEG 或 PNG。
这是一个将(3 字节 r,g,b )字节的原始数据转化为图像的示例
int width = 256;
int height = 256;
ByteBuffer bb = ByteBuffer.allocateDirect(height*width*3);
byte[] raw = new byte[width * height * 3];
bb.get(raw);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
DataBuffer buffer = new DataBufferByte(raw, raw.length);
SampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 3, width * 3, new int[]{0,1,2});
Raster raster = Raster.createRaster(sampleModel, buffer, null);
image.setData(raster);
更新 2
对于 BGR32,我相信这会更接近:
ByteBuffer imageData = ByteBuffer.allocateDirect(height * width * 4);
byte[] raw = new byte[width * height * 4];
imageData.get(raw);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
DataBuffer buffer = new DataBufferByte(raw, raw.length);
SampleModel sampleModel = new ComponentSampleModel(
DataBuffer.TYPE_BYTE, width, height, 4, width * 4,
new int[]{2,1,0} // Try {1,2,3}, {3,2,1}, {0,1,2}
);
Raster raster = Raster.createRaster(sampleModel, buffer, null);
image.setData(raster);
注意我评论的地方,我怀疑您可能需要在 ComponentSampleModel 构造函数的第三个参数中试验 bandOffsets 数组来修复颜色模型。
更新 3
可以通过对 WritableRaster 使用 BufferedImage.copyData() 而不是使用 getRaster() 来重用 sampleModel 从图像中获取数据。
SampleModel sampleModel = new ComponentSampleModel(
DataBuffer.TYPE_BYTE, width, height, 4, width * 4,
new int[]{2, 1, 0}
);
...
BufferedImage newImage = ImageIO.read(new File("test.png"));
byte newRaw[] = new byte[height*width*4];
DataBuffer newBuffer = new DataBufferByte(newRaw, newRaw.length);
WritableRaster newRaster = Raster.createWritableRaster(sampleModel, newBuffer, null);
newImage.copyData(newRaster);