How to correctly use ImageReader with YUV_420_888 and MediaCodec to encode video to h264 format? - camera

I'm implementing a camera application on Android devices. Currently, I use Camera2 API and ImageReader to get image data in YUV_420_888 format, but I don't know how to exactly write these data to MediaCodec.
Here are my questions:
What is YUV_420_888?
The format YUV_420_888 is ambiguous because it can be any format which belongs to the YUV420 family, such as YUV420P, YUV420PP, YUV420SP and YUV420PSP, right?
By accessing the image's three planes(#0, #1, #2), I can get the Y(#0), U(#1), V(#2) values of this image. But the arrangement of these values may not be the same on different devices. For example, if YUV_420_888 truly means YUV420P, the size of both Plane#1 and Plane#2 is a quarter of the size of Plane#0. If YUV_420_888 truly means YUV420SP, the size of both Plane#1 and Plane#2 is half of the size of Plane#0(Each of Plane#1 and Plane#2 contains U, V values).
If I want to write these data from image's three planes to MediaCodec, what kind of format I need to convert to? YUV420, NV21, NV12, ...?
What is COLOR_FormatYUV420Flexible?
The format COLOR_FormatYUV420Flexible is also ambiguous because it can be any format which belongs to the YUV420 family, right? If I set KEY_COLOR_FORMAT option of a MediaCodec object to COLOR_FormatYUV420Flexible, what format(YUV420P, YUV420SP...?) of data should I input to the MediaCodec object?
How about using COLOR_FormatSurface?
I know MediaCodec has its own surface, which can be used if I set KEY_COLOR_FORMAT option of a MediaCodec object to COLOR_FormatSurface. And with Camera2 API, I don't need to write any data by myself to the MediaCodec object. I can just drain the output buffer.
However, I need to change the image from the camera. For example, draw other pictures, write some text on it, or insert another video as POP(Picture of Picture).
Can I use ImageReader to read the image from Camera, and after re-drawing that, write the new data to MediaCodec's surface, and then drain it out? How to do that?
EDIT1
I implemented the function by using COLOR_FormatSurface and RenderScript. Here is my code:
onImageAvailable method:
public void onImageAvailable(ImageReader imageReader) {
try {
try (Image image = imageReader.acquireLatestImage()) {
if (image == null) {
return;
}
Image.Plane[] planes = image.getPlanes();
if (planes.length >= 3) {
ByteBuffer bufferY = planes[0].getBuffer();
ByteBuffer bufferU = planes[1].getBuffer();
ByteBuffer bufferV = planes[2].getBuffer();
int lengthY = bufferY.remaining();
int lengthU = bufferU.remaining();
int lengthV = bufferV.remaining();
byte[] dataYUV = new byte[lengthY + lengthU + lengthV];
bufferY.get(dataYUV, 0, lengthY);
bufferU.get(dataYUV, lengthY, lengthU);
bufferV.get(dataYUV, lengthY + lengthU, lengthV);
imageYUV = dataYUV;
}
}
} catch (final Exception ex) {
}
}
Convert YUV_420_888 to RGB:
public static Bitmap YUV_420_888_toRGBIntrinsics(Context context, int width, int height, byte[] yuv) {
RenderScript rs = RenderScript.create(context);
ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuv.length);
Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
Bitmap bmpOut = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
in.copyFromUnchecked(yuv);
yuvToRgbIntrinsic.setInput(in);
yuvToRgbIntrinsic.forEach(out);
out.copyTo(bmpOut);
return bmpOut;
}
MediaCodec:
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
...
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
...
surface = mediaCodec.createInputSurface(); // This surface is not used in Camera APIv2. Camera APIv2 uses ImageReader's surface.
And in athother thread:
while (!stop) {
final byte[] image = imageYUV;
// Do some yuv computation
Bitmap bitmap = YUV_420_888_toRGBIntrinsics(getApplicationContext(), width, height, image);
Canvas canvas = surface.lockHardwareCanvas();
canvas.drawBitmap(bitmap, matrix, paint);
surface.unlockCanvasAndPost(canvas);
}
This way works, but the performance is not good. It can't output 30fps video files(only ~12fps). Perhaps I should not use COLOR_FormatSurface and the surface's canvas for encoding. The computed YUV data should be written to the mediaCodec directly without any surface doing any conversion. But I still don't know how to do that.

You are right, YUV_420_888 is a format that can wrap different YUV 420 formats. The spec carefully explains that the arrangement of U and V planes is not prescribed, but there are certain restrictions; e.g. if the U plane has pixel stride 2, same applies to V (and then the underlying byte buffer can be NV21).
COLOR_FormatYUV420Flexible is a synonym of YUV_420_888, but they belong to different classes: MediaCodec and ImageFormat, respectively.
The spec explains:
All video codecs support flexible YUV 4:2:0 buffers since LOLLIPOP_MR1.
COLOR_FormatSurface is an opaque format that can deliver best performance for MediaCodec, but this comes at price: you cannot directly read or manipulate its content. If you need to manipulate the data that goes to the MediaCodec, then using ImageReader is an option; whether it will be more efficient than ByteBuffer, depends on what you do and how you do it. Note that for API 24+ you can work with both camera2 and MediaCodec in C++.
The invaluable resource of details for MediaCodec is http://www.bigflake.com/mediacodec. It references a full example of 264 encoding.

Create a textureID -> SurfaceTexture -> Surface -> Camera 2 -> onFrameAvaliable -> updateTexImage -> glBindTexture -> draw something -> swapbuffer to Mediacodec's inputSurface.

Related

How do I create an image from an RGBA byte array in Java?

I want to implement capture over time by sending RDP screen data to Java using freerdp library written in C.
I found the unsigned char* type data value in the C module structure and extracted the image using the stb_image library. and success.
stbi_write_bmp(locationPath, int width, int height, 4,unsign char* data); // simply succeeded.
Using JNI - JByteArray, I received the corresponding value as a byte[] from JAVA, and I want to extract this byte[] value as an image.
public static void JNIcallback2(int width, int height,byte[] data) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
BufferedImage bufferedImage = ImageIO.read(inputStream);
ImageIO.write(bufferedImage, "png", new File("/Users/kimfk567/Documents/javaJNI_Image/data6.png"));
}
but error
Exception in thread "Thread-4" java.lang.IllegalArgumentException: image == null!
at java.desktop/javax.imageio.ImageTypeSpecifier.createFromRenderedImage(ImageTypeSpecifier.java:925)
at java.desktop/javax.imageio.ImageIO.getWriter(ImageIO.java:1610)
at java.desktop/javax.imageio.ImageIO.write(ImageIO.java:1542)
The Android implementation of Freerdp uses the Bitmap class to process data, but I can't use Android Studio.
https://developer.android.com/reference/android/graphics/BitmapFactory
byte[] to hex string
This value is estimated as 4-byte RGBA and is not extracted from a specific image.
AND
It seems to be pixel data of a bitmap.
To output this value as an image, I converted unsigned char* to int* in C, received it as int[] in JAVA, and also tried WritableRaster.setPixels
BufferedImage a = new BufferedImage(1024,768,2);
WritableRaster raster = (WritableRaster) a.getData();
raster.setPixels(0,0,1024,768, convertData);
a.setData(raster);
ImageIO.write(bufferedImage, "jpg", new File("/Users/Documents/javaJNI_Image/data.jpg"))
but failed..
I invested a lot of time but couldn't solve it.

What are the normal methods for achiving texture mapping with raytracing?

When you create a BLAS (bottom level acceleration structures) you specify any number of vertex/index buffers to be part of the structure. How does that end up interacting with the shader and get specified in the descriptor set? How should I link these structures with materials?
How is texture mapping usually done with raytracing? I saw some sort of "materials table" in Q2RTX but the documentation is non-existent and the code is sparsely commented.
A common approach is to use a material buffer in combination with a texture array that is addressed in the shaders where you require the texture data. You then pass the material id e.g. per-vertex or per-primitive and then use that to dynamically fetch the material, and with it the texture index. Due to the requirements for Vulkan ray tracing you can simplify this by using the VK_EXT_descriptor_indexing extension (Spec) that makes it possible to create a large and descriptor set containing all textures required to render your scene.
The relevant shader parts:
// Enable required extension
...
#extension GL_EXT_nonuniform_qualifier : enable
// Material definition
struct Material {
int albedoTextureIndex;
int normalTextureIndex;
...
};
// Bindings
layout(binding = 6, set = 0) readonly buffer Materials { Material materials[]; };
layout(binding = 7, set = 0) uniform sampler2D[] textures;
...
// Usage
void main()
{
Primitive primitive = unpackTriangle(gl_Primitive, ...);
Material material = materials[primitive.materialId];
vec4 color = texture(textures[nonuniformEXT(material.albedoTextureIndex)], uv);
...
}
In your application you then create a buffer that stores the materials generated on the host, and bind it to the binding point of the shader.
For the textures, you pass them as an array of textures. An array texture would be an option too, but isn't as flexible due to the same size per array slice limitation. Note that it does not have a size limitation in the above example, which is made possible by VK_EXT_descriptor_indexing and is only allowed for the final binding in a descriptor set. This adds some flexibility to your setup.
As for the passing the material index that you fetch the data from: The easiest approach is to pass that information along with your vertex data, which you'll have to access/unpack in your shaders anyway:
struct Vertex {
vec4 pos;
vec4 normal;
vec2 uv;
vec4 color;
int32_t materialIndex;
}

What image format should be employed for real time processing using Camera2 in android?

I am developing an android application which processes Camera2 preview frames and displays processed frames on the Texture. At first, I tested with camera1 api, it works fine for real time image processing.
private class CameraPreviewCallback implements Camera.PreviewCallback {
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
processingRunnable.setNextFrame(data, camera);
}
}
Then, I changed my code which utilizes camera2 api. For getting preview frames, I set ImageFormat as YUV_420_888
mImageReaderPreview = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 3);
mImageReaderPreview.setOnImageAvailableListener(mOnPreviewAvailableListener, mBackgroundHandler);
private final ImageReader.OnImageAvailableListener mOnPreviewAvailableListener = new ImageReader.OnImageAvailableListener() {
#Override
public void onImageAvailable(ImageReader reader) {
Image mImage = reader.acquireLatestImage();
if(mImage == null) {
return;
}
processingRunnable.setNextFrame(convertYUV420888ToNV21(mImage));
mImage.close();
}
};
However, it's working slower than camera1. May be it's because of having one extra conversion from YUV_420_888 to NV21. Since Camera1 can directly provides NV21 frame from Camera1.
Conversion could be expensive, depending on how you implement it and what the layout of the YUV_420_888 on a given device is.
Certainly if it's written in pure Java is probably going to be slow.
That said, if the device you're using is at the LEGACY hardware level, camera2 has to run in a legacy mode that can be slow for receiving YUV information. For those devices, staying on API1 may be preferable for your use case.

Corrupt when using Google Play Services Face Detection in Picasso Transformation. (Fatal Signal)

I'm writing a face crop transformation of Picasso.
This is to make the face in image would be shown completely when Picasso cropping an image.
So I implement a Picasso Transformation and using Face Detection in the function public Bitmap transform(Bitmap source).
import com.squareup.picasso.Transformation;
....
public class FaceCrop implements Transformation {
...
#Override public Bitmap transform(Bitmap source) {
FaceDetector mFaceDetector = new FaceDetector.Builder(mContext).setTrackingEnabled(false).build();
if (!mFaceDetector.isOperational()) {
return source;
}
Frame frame = new Frame.Builder().setBitmap(source).build();
SparseArray<Face> faces = mFaceDetector.detect(frame); // sometimes crash here
...
RectF rect = null;
for (int i = 0; i < faces.size(); i++) {
Face face = faces.valueAt(i);
float x = face.getPosition().x;
float y = face.getPosition().y;
RectF faceRect = new RectF(x, y, x + face.getWidth(), y + face.getHeight());
if (rect == null) {
rect = faceRect;
continue;
}
rect.union(faceRect);
}
mFaceDetector.release();
...
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, source.getConfig());
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(source, null, targetRect, null);
source.recycle();
return bitmap;
}
}
For most images, it works perfectly. Nothing wrong.
But for some specific images, sometimes it works fine, sometimes it crashes at SparseArray<Face> faces = mFaceDetector.detect(frame);. (it's not always crash for one image.)
I tried to use try and catch block to catch this error, it still crash anyway. So I can't skip it.
And the error messages are different every time, but they are similar.
They are all like "Fatal signal x (SIGABRT)...".
Here are 4 examples :
1.
invalid address or address of corrupt block 0xba362ba0 passed to dlfree
Fatal signal 11 (SIGSEGV), code 1, fault addr 0xdeadbaad in tid 10651 (Picasso-/extern)
2.
Fatal signal 11 (SIGSEGV), code 2, fault addr 0x93948000 in tid 16443 (Picasso-/extern)
3.
Fatal signal 11 (SIGSEGV), code 1, fault addr 0x605f5e6a in tid 17951 (GCDaemon)
4.
heap corruption detected by tmalloc_large
Fatal signal 6 (SIGSEGV), code -6 in tid 19020 (Picasso-/extern)
I am sure the parameter frame of function detect is not null.
I checked those images which sometimes cause the crash, I can't see anything wrong with them.
They looks normal, and they are not very large files (smaller than 1M).
Some with faces, some are not.
And they all can be decoded normally by Picasso with Picasso's center crop.
Can anyone give me some suggestion?
Thank you so much!!
There is a known issue with small images, and this will be fixed in a future release. But fortunately, there is a patch available on GitHub now:
https://github.com/googlesamples/android-vision/issues/26
If you are still getting this issue even with that patch, please print out some of the image sizes and post here to help us to debug.

How to use ClearType with double buffering on Compact Framework?

When I draw a string into a buffer, the resulting output is not anti-aliased the way I'd expect. This code illustrates the problem... just put this in a standard smart device project's Form1.cs:
protected override void OnPaint(PaintEventArgs e)
{
Bitmap buffer = new Bitmap(Width, Height, PixelFormat.Format32bppRgb);
using(Graphics g = Graphics.FromImage(buffer))
{
g.Clear(Color.White);
g.DrawString("Hello, World", Font, new SolidBrush(Color.Black), 5, 5);
}
e.Graphics.DrawImage(buffer, 0, 0);
}
On the other hand, if I just draw the string into the Graphics object that was passed in with the PaintEventArgs, it renders in ClearType just as I'd expect.
I figure I've got to create my Graphics buffer in a way that makes it use font smoothing, but I don't see a way to do that.
Turns out to have been a simple problem. By removing the PixelFormat.Format32bppRgb it worked fine. Looks like you need to make sure your buffers have the same pixel formats...
Set the SmoothingMode property of your Graphics object:
g.SmoothingMode = SmoothingMode.AntiAlias;
You will have to use gdiplus.dll (there exists a few wrappers for this), but it is only available on Windows Mobile 6 Professional (not Standard).