7

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:

  1. 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, ...?

  1. 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?

  1. 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.

user3032481
  • 548
  • 6
  • 18
  • Did you get any solution for this. If yes, can you post the answer? – Shivam Pokhriyal Jun 19 '19 at 03:27
  • @ShivamPokhriyal No solutions for the standard SDK as far as I know. – user3032481 Jun 19 '19 at 07:14
  • So do you know any workaround for this? – Shivam Pokhriyal Jun 19 '19 at 08:04
  • @user3032481 hi, old post but I am now facing the same thing... trying to feed CameraX ImageAnalysis image into media encoder of format COLOR_FormatYUV420Flexible and Im quite lost... funny thing, the number of bytes returned by CameraX yuv planes is not the same as expected by the encoder (when calling to getInputBuffer). Also, the number of bytes returned by the encoder getInputBuffer is different if I use landscape or portrait (1024*768 vs 768*1024), it's quite special that YUV format... – ValYouW Jan 09 '23 at 00:41

2 Answers2

5

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.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • Thanks. I've tried to encode video by COLOR_FormatSurface, but the performance is not good(see my EDIT1 section). Is there any example about encoding video by using COLOR_FormatYUV420Flexible(or other YUV format)? – user3032481 Sep 26 '17 at 07:20
  • If I understand correctly, you intend to add some more code in **onImageAvailable()**, so that **imageYUV** will be an edited copy of the camera frame. Because otherwise there is no reason to simply copy the planes from **image** to another ByteBuffer. – Alex Cohn Sep 26 '17 at 08:35
  • At any rate, even with renderscript your display procedure is suboptimal. It may be much faster to use OpenGL directly, with a shader that converts YUV420 input to RGB on the fly. – Alex Cohn Sep 26 '17 at 08:38
  • I know imageYUV is odd. But actually I have mutiple camera sources. I use variables to store frames of each camera at the same moment. And use a different thread to compute the frames which are from each camera at this moment, and write the result to a video file. By the way, how to use OpenGL to convert YUV420_888 to RGB? – user3032481 Sep 26 '17 at 14:14
  • The trick is not to 'convert' YUV to RGB, but rather use OpenGL shader that can take input as YUV and transform it into a texture that can be displayed. This way, you don't need to read RGB data, it goes directly to the screen buffer. Please check [this example](http://bigflake.com/mediacodec/#DecodeEditEncodeTest) for some hints. – Alex Cohn Sep 26 '17 at 14:28
1

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

BC.Lee
  • 11
  • 3