目次
Java NV21toYUV420SemiPlanar エンコーディングを使用してビデオを保存します。
jni NV12toYUV420SemiPlanar 関数:
ブログからのコード:
Javaイメージリーダーエンコーディング保存
//Planar格式(P)的处理
private static ByteBuffer getuvBufferWithoutPaddingP(ByteBuffer uBuffer,ByteBuffer vBuffer, int width, int height, int rowStride, int pixelStride){
int pos = 0;
byte []byteArray = new byte[height*width/2];
for (int row=0; row<height/2; row++) {
for (int col=0; col<width/2; col++) {
int vuPos = col*pixelStride + row*rowStride;
byteArray[pos++] = vBuffer.get(vuPos);
byteArray[pos++] = uBuffer.get(vuPos);
}
}
ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
// 数组放到buffer中
bufferWithoutPaddings.put(byteArray);
//重置 limit 和postion 值否则 buffer 读取数据不对
bufferWithoutPaddings.flip();
return bufferWithoutPaddings;
}
//Semi-Planar格式(SP)的处理和y通道的数据
private static ByteBuffer getBufferWithoutPadding(ByteBuffer buffer, int width, int rowStride, int times,boolean isVbuffer){
if(width == rowStride) return buffer; //没有buffer,不用处理。
int bufferPos = buffer.position();
int cap = buffer.capacity();
byte []byteArray = new byte[times*width];
int pos = 0;
//对于y平面,要逐行赋值的次数就是height次。对于uv交替的平面,赋值的次数是height/2次
for (int i=0;i<times;i++) {
buffer.position(bufferPos);
//part 1.1 对于u,v通道,会缺失最后一个像u值或者v值,因此需要特殊处理,否则会crash
if(isVbuffer && i==times-1){
width = width -1;
}
buffer.get(byteArray, pos, width);
bufferPos+= rowStride;
pos = pos+width;
}
//nv21数组转成buffer并返回
ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
// 数组放到buffer中
bufferWithoutPaddings.put(byteArray);
//重置 limit 和postion 值否则 buffer 读取数据不对
bufferWithoutPaddings.flip();
return bufferWithoutPaddings;
}
private static byte[] YUV_420_888toNV21(Image image) {
int width = image.getWidth();
int height = image.getHeight();
ByteBuffer yBuffer = getBufferWithoutPadding(image.getPlanes()[0].getBuffer(), image.getWidth(), image.getPlanes()[0].getRowStride(),image.getHeight(),false);
ByteBuffer vBuffer;
//part1 获得真正的消除padding的ybuffer和ubuffer。需要对P格式和SP格式做不同的处理。如果是P格式的话只能逐像素去做,性能会降低。
if(image.getPlanes()[2].getPixelStride()==1){ //如果为true,说明是P格式。
vBuffer = getuvBufferWithoutPaddingP(image.getPlanes()[1].getBuffer(), image.getPlanes()[2].getBuffer(),
width,height,image.getPlanes()[1].getRowStride(),image.getPlanes()[1].getPixelStride());
}else{
vBuffer = getBufferWithoutPadding(image.getPlanes()[2].getBuffer(), image.getWidth(), image.getPlanes()[2].getRowStride(),image.getHeight()/2,true);
}
//part2 将y数据和uv的交替数据(除去最后一个v值)赋值给nv21
int ySize = yBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21;
int byteSize = width*height*3/2;
nv21 = new byte[byteSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
//part3 最后一个像素值的u值是缺失的,因此需要从u平面取一下。
ByteBuffer uPlane = image.getPlanes()[1].getBuffer();
byte lastValue = uPlane.get(uPlane.capacity() - 1);
nv21[byteSize - 1] = lastValue;
return nv21;
}
Java NV21toYUV420SemiPlanar エンコーディングを使用してビデオを保存します。
public byte[] NV21toYUV420SemiPlanar(byte[] nv21, int width, int height) {
byte[] yuv420sp = new byte[width * height * 3 / 2];
int frameSize = width * height;
int i, j;
System.arraycopy(nv21, 0, yuv420sp, 0, frameSize); // Y分量直接复制
for (i = 0; i < frameSize / 4; i++) {
j = i * 2;
// NV21的UV分量交替排列,转为NV12需要调换U和V的位置
yuv420sp[frameSize + j] = nv21[frameSize + j + 1]; // U分量
yuv420sp[frameSize + j + 1] = nv21[frameSize + j]; // V分量
}
return yuv420sp;
}
imageReader が nv21 を取得
平面[0] + 平面[2] =NV21;; 平面[0] + 平面[1] = NV12
Image image = reader.acquireLatestImage();
if (image == null) {
return;
}
Image.Plane[] planes = image.getPlanes();
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
int ySize = yBuffer.remaining();
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
int vSize = vBuffer.remaining();
byte[] nv21_s = new byte[WIDTH*HEIGHT * 3 / 2];
try {
yBuffer.get(nv21_s, 0, yBuffer.remaining());
vBuffer.get(nv21_s, ySize, vBuffer.remaining());
imageQueue.put(nv21_s);
} catch (Exception e) {
throw new RuntimeException(e);
}
image.close();
注記:
1. この方法では、最後のピクセルの U 成分または V 成分が欠落します。完全性を追求する場合、NV21 の場合は、plane[1] から最後の値を取得して最後に追加できます。NV12 の場合は、plane[1] から取得して最後に追加できます。 plan[2] 最後の値が末尾に追加されます。
2. 画像の幅が 8 の整数倍である場合にのみ適用されます。それ以外の場合は、メモリの配置が必要なため、後で 0 が追加され、image.getWidth()<plane.getRowStride() となります。これには、各行で後者を破棄する必要があります。余分な 0 を破棄してからスプライシングすると、効率は大幅に低下します。
jni NV12toYUV420SemiPlanar 関数:
public native byte[] NV12toYUV420SemiPlanar(byte[] data, int w, int h);
extern "C"
JNIEXPORT jbyteArray JNICALL Java_com_sandstar_ai_objectdetector_ObjectDetector_NV12toYUV420SemiPlanar
(JNIEnv* env, jobject, jbyteArray nv12, jint width, jint height) {
jbyte* nv12_bytes = env->GetByteArrayElements(nv12, NULL);
jsize nv21_length = env->GetArrayLength(nv12);
int frameSize = width * height;
std::vector<uint8_t> yuv420sp(nv21_length);
std::memcpy(yuv420sp.data(), nv12_bytes, frameSize); // copy Y
for (int i = 0; i < frameSize / 4; ++i) {
int j = i * 2;
yuv420sp[frameSize + j] = nv12_bytes[frameSize + j + 1]; // copy V
yuv420sp[frameSize + j + 1] = nv12_bytes[frameSize + j]; // copy U
}
env->ReleaseByteArrayElements(nv12, nv12_bytes, 0);
// Create a new byte array and put the data into it
jbyteArray result = env->NewByteArray(nv21_length);
env->SetByteArrayRegion(result, 0, nv21_length, reinterpret_cast<jbyte*>(yuv420sp.data()));
return result;
}