如何正确使用ImageReader与YUV_420_888和MediaCodec将视频编码为h264格式?

时间:2017-09-25 11:17:56

标签: camera android-camera yuv android-camera2

我正在Android设备上实现相机应用程序。目前,我使用Camera2 API和ImageReader来获取YUV_420_888格式的图像数据,但我不知道如何将这些数据准确地写入MediaCodec。

以下是我的问题:

  1. 什么是YUV_420_888
  2. 格式YUV_420_888不明确,因为它可以是属于YUV420系列的任何格式,例如YUV420PYUV420PPYUV420SP和{ {1}},对吧?

    通过访问图像的三个平面(#0,#1,#2),我可以得到该图像的Y(#0),U(#1),V(#2)值。但是这些值的排列在不同设备上可能不同。例如,如果YUV420PSP确实意味着YUV_420_888,则平面#1和平面#2的大小都是平面#0大小的四分之一。如果YUV420P确实意味着YUV_420_888,则平面#1和平面#2的大小都是平面#0的大小的一半(平面#1和平面#2中的每一个包含U,V值)

    如果我想将这些数据从图像的三个平面写入MediaCodec,我需要将哪种格式转换为? YUV420,NV21,NV12,......?

    1. 什么是YUV420SP
    2. 格式COLOR_FormatYUV420Flexible也不明确,因为它可以是属于COLOR_FormatYUV420Flexible系列的任何格式,对吗?如果我将MediaCodec对象的YUV420选项设置为KEY_COLOR_FORMAT,我应该将哪种格式(YUV420P,YUV420SP ......?)输入到MediaCodec对象中?

      1. 如何使用COLOR_FormatYUV420Flexible
      2. 我知道MediaCodec有自己的表面,如果我将MediaCodec对象的COLOR_FormatSurface选项设置为KEY_COLOR_FORMAT,则可以使用它。使用Camera2 API,我不需要自己将任何数据写入MediaCodec对象。我可以耗尽输出缓冲区。

        但是,我需要从相机更改图像。例如,绘制其他图片,在其上书写一些文字,或将另一个视频插入POP(图片图片)。

        我可以使用ImageReader从Camera中读取图像,重新绘制后,将新数据写入MediaCodec的表面,然后将其排出吗?怎么做?

        EDIT1

        我使用COLOR_FormatSurface和RenderScript实现了该功能。这是我的代码:

        COLOR_FormatSurface方法:

        onImageAvailable

        将YUV_420_888转换为RGB:

        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) {
        
            }
        }
        

        MediaCodec:

        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;
        }
        

        在athother主题中:

        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.
        

        这种方式有效,但性能不佳。它无法输出30fps的视频文件(仅约12fps)。也许我不应该使用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); } 和表面的画布进行编码。计算出的YUV数据应该直接写入mediaCodec,而不需要任何表面进行任何转换。但我仍然不知道该怎么做。

2 个答案:

答案 0 :(得分:1)

你是对的,YUV_420_888是一种可以包装不同YUV 420格式的格式。该规范仔细解释了U和V飞机的布置没有规定,但有一定的限制;例如如果U平面具有像素步幅2,则同样适用于V(然后基础字节缓冲器可以是NV21)。

COLOR_FormatYUV420FlexibleYUV_420_888的同义词,但它们分别属于不同的类:MediaCodec和ImageFormat。

规范解释说:

  

LOLLIPOP_MR1以来,所有视频编解码器都支持灵活的YUV 4:2:0缓冲区。

COLOR_FormatSurface是一种不透明的格式,可以为MediaCodec提供最佳性能,但这需要付出代价:您无法直接阅读或操纵其内容。如果您需要操作传递给MediaCodec的数据,那么使用ImageReader是一个选项;它是否比ByteBuffer更有效,取决于你做了什么以及如何做。请注意,对于API 24+,您可以在C ++中使用camera2和MediaCodec。

MediaCodec的宝贵资源包括http://www.bigflake.com/mediacodec。它引用了full example 264编码。

答案 1 :(得分:0)

创建一个textureID -> SurfaceTexture -> Surface -> Camera 2 -> onFrameAvaliable -> updateTexImage -> glBindTexture -> draw something -> swapbuffer to Mediacodec's inputSurface。