Android的创建从屏幕抓取视频:为什么是输出图像靠不住?

问题描述 投票:0回答:1

更新#6我发现不当访问RGB值。我假定我是从一个Int []访问数据,而是从一个字节[]字节的信息代替访问。改变由Int访问[]和获得下面的图片:

enter image description here


更新#5添加代码用来获取RGBA字节缓冲区以供参考

 private void screenScrape() {

    Log.d(TAG, "In screenScrape");

    //read pixels from frame buffer into PBO (GL_PIXEL_PACK_BUFFER)
    mSurface.queueEvent(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "In Screen Scrape 1");
            //generate and bind buffer ID
            GLES30.glGenBuffers(1, pboIds);
            checkGlError("Gen Buffers");
            GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pboIds.get(0));
            checkGlError("Bind Buffers");

            //creates and initializes data store for PBO.  Any pre-existing data store is deleted
            GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, (mWidth * mHeight * 4), null, GLES30.GL_STATIC_READ);
            checkGlError("Buffer Data");

            //glReadPixelsPBO(0,0,w,h,GLES30.GL_RGB,GLES30.GL_UNSIGNED_SHORT_5_6_5,0);
            glReadPixelsPBO(0, 0, mWidth, mHeight, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, 0);

            checkGlError("Read Pixels");
            //GLES30.glReadPixels(0,0,w,h,GLES30.GL_RGBA,GLES30.GL_UNSIGNED_BYTE,intBuffer);
        }
    });

    //map PBO data into client address space
    mSurface.queueEvent(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "In Screen Scrape 2");

            //read pixels from PBO into a byte buffer for processing.  Unmap buffer for use in next pass
            mapBuffer = ((ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, 4 * mWidth * mHeight, GLES30.GL_MAP_READ_BIT)).order(ByteOrder.nativeOrder());
            checkGlError("Map Buffer");

            GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);
            checkGlError("Unmap Buffer");

            isByteBufferEmpty(mapBuffer, "MAP BUFFER");
            convertColorSpaceByteArray(mapBuffer);
            mapBuffer.clear();
        }
    });
}

更新#4作为参考,这里是原始图像进行比较的。

enter image description here


更新#3这是所有U / V数据交织成一个单一的阵列,并在inputImagePlanes[1]; inputImagePlanes[2];它传递给图像对象是未使用后的输出图像; enter image description here

接下来的图像是一样的交错UV数据,但我们加载到这个inputImagePlanes[2];而不是inputImagePlanes[1]; enter image description here


更新#2这是“真正的”数据的每个字节之间填充以零的U / V缓冲器之后的输出图像。 uArray[uvByteIndex] = (byte) 0;

enter image description here


更新#1正如评论所说,这里的行和像素的进步与调用getPixelStridegetRowStride我得到

Y Plane Pixel Stride = 1, Row Stride = 960
U Plane Pixel Stride = 2, Row Stride = 960
V Plane Pixel Stride = 2, Row Stride = 960

我的应用程序的目标是从屏幕上读出来的像素,对其进行压缩,然后发送h264码流通过WiFi进行播放是一个接收器。

目前我使用的MediaMuxer类原料h264码流转换为MP4,然后将其保存到文件。然而最终的结果视频搞砸了,我想不通为什么。让我们通过一些处理的散步,看看是否能找到任何跳出。

第1步设置编码器。对于MIME_TYPE我目前拍摄画面图像每2秒一次,并使用“视频/ AVC”

        //create codec for compression
        try {
            mCodec = MediaCodec.createEncoderByType(MIME_TYPE);
        } catch (IOException e) {
            Log.d(TAG, "FAILED: Initializing Media Codec");
        }

        //set up format for codec
        MediaFormat mFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);

        mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        mFormat.setInteger(MediaFormat.KEY_BIT_RATE, 16000000);
        mFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 1/2);
        mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);

第2步阅读从屏幕像素了。这是使用OpenGL ES的完成,并且像素的RGBA格式读出。 (我已经证实了这一部分是工作)

步骤3转换的RGBA像素YUV420(IYUV)格式。这是使用下面的方法来完成。请注意,我有2个方法叫做这个方法结束编码。

 private void convertColorSpaceByteArray(ByteBuffer rgbBuffer) {

    long startTime = System.currentTimeMillis();

    Log.d(TAG, "In convertColorspace");
    final int frameSize = mWidth * mHeight;
    final int chromaSize = frameSize / 4;

    byte[] rgbByteArray = new byte[rgbBuffer.remaining()];
    rgbBuffer.get(rgbByteArray);

    byte[] yuvByteArray = new byte[inputBufferSize];
    Log.d(TAG, "Input Buffer size = " + inputBufferSize);

    byte[] yArray = new byte[frameSize];
    byte[] uArray = new byte[(frameSize / 4)];
    byte[] vArray = new byte[(frameSize / 4)];

    isByteBufferEmpty(rgbBuffer, "RGB BUFFER");

    int yIndex = 0;
    int uIndex = frameSize;
    int vIndex = frameSize + chromaSize;

    int yByteIndex = 0;
    int uvByteIndex = 0;

    int R, G, B, Y, U, V;
    int index = 0;

    //this loop controls the rows
    for (int i = 0; i < mHeight; i++) {
        //this loop controls the columns
        for (int j = 0; j < mWidth; j++) {

            R = (rgbByteArray[index] & 0xff0000) >> 16;
            G = (rgbByteArray[index] & 0xff00) >> 8;
            B = (rgbByteArray[index] & 0xff);

            Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
            U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
            V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

            //clamp and load in the Y data
            yuvByteArray[yIndex++] = (byte) ((Y < 16) ? 16 : ((Y > 235) ? 235 : Y));
            yArray[yByteIndex] = (byte) ((Y < 16) ? 16 : ((Y > 235) ? 235 : Y));
            yByteIndex++;

            if (i % 2 == 0 && index % 2 == 0) {
                //clamp and load in the U & V data
                yuvByteArray[uIndex++] = (byte) ((U < 16) ? 16 : ((U > 239) ? 239 : U));
                yuvByteArray[vIndex++] = (byte) ((V < 16) ? 16 : ((V > 239) ? 239 : V));

                uArray[uvByteIndex] = (byte) ((U < 16) ? 16 : ((U > 239) ? 239 : U));
                vArray[uvByteIndex] = (byte) ((V < 16) ? 16 : ((V > 239) ? 239 : V));

                uvByteIndex++;
            }
            index++;
        }
    }
    encodeVideoFromImage(yArray, uArray, vArray);
    encodeVideoFromBuffer(yuvByteArray);
}

步骤4编码的数据!我现在有这样的两种不同方式,各有不同的输出。其中使用从ByteBuffer返回MediaCodec.getInputBuffer();,另一个使用的ImageMediaCodec.getInputImage();返回

编码使用ByteBuffer

 private void encodeVideoFromBuffer(byte[] yuvData) {

    Log.d(TAG, "In encodeVideo");
    int inputSize = 0;

    //create index for input buffer
    inputBufferIndex = mCodec.dequeueInputBuffer(0);
    //create the input buffer for submission to encoder
    ByteBuffer inputBuffer = mCodec.getInputBuffer(inputBufferIndex);


    //clear, then copy yuv buffer into the input buffer
    inputBuffer.clear();
    inputBuffer.put(yuvData);

    //flip buffer before reading data out of it
    inputBuffer.flip();

    mCodec.queueInputBuffer(inputBufferIndex, 0, inputBuffer.remaining(), presentationTime, 0);

    presentationTime += MICROSECONDS_BETWEEN_FRAMES;

    sendToWifi();
}

而相关的输出图像(注:我把MP4的屏幕截图)enter image description here

编码使用Image

 private void encodeVideoFromImage(byte[] yToEncode, byte[] uToEncode, byte[]vToEncode) {

    Log.d(TAG, "In encodeVideo");
    int inputSize = 0;

    //create index for input buffer
    inputBufferIndex = mCodec.dequeueInputBuffer(0);
    //create the input buffer for submission to encoder
    Image inputImage = mCodec.getInputImage(inputBufferIndex);
    Image.Plane[] inputImagePlanes = inputImage.getPlanes();

    ByteBuffer yPlaneBuffer = inputImagePlanes[0].getBuffer();
    ByteBuffer uPlaneBuffer = inputImagePlanes[1].getBuffer();
    ByteBuffer vPlaneBuffer = inputImagePlanes[2].getBuffer();

    yPlaneBuffer.put(yToEncode);
    uPlaneBuffer.put(uToEncode);
    vPlaneBuffer.put(vToEncode);

    yPlaneBuffer.flip();
    uPlaneBuffer.flip();
    vPlaneBuffer.flip();

    mCodec.queueInputBuffer(inputBufferIndex, 0, inputBufferSize, presentationTime, 0);

    presentationTime += MICROSECONDS_BETWEEN_FRAMES;

    sendToWifi();
}

而相关的输出图像(注:我把MP4的屏幕截图)enter image description here

第5步转换H264流的MP4。最后,我抓住从编解码器输出缓冲区,并使用MediaMuxer将原始h264码流转换为MP4,我可以发挥的正确性和测试

 private void sendToWifi() {
    Log.d(TAG, "In sendToWifi");

    MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();

    //Check to see if encoder has output before proceeding
    boolean waitingForOutput = true;
    boolean outputHasChanged = false;
    int outputBufferIndex = 0;

    while (waitingForOutput) {
        //access the output buffer from the codec
        outputBufferIndex = mCodec.dequeueOutputBuffer(mBufferInfo, -1);

        if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            outputFormat = mCodec.getOutputFormat();
            outputHasChanged = true;
            Log.d(TAG, "OUTPUT FORMAT HAS CHANGED");
        }

        if (outputBufferIndex >= 0) {
            waitingForOutput = false;
        }
    }

    //this buffer now contains the compressed YUV data, ready to be sent over WiFi
    ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufferIndex);

    //adjust output buffer position and limit.  As of API 19, this is not automatic
    if(mBufferInfo.size != 0) {
        outputBuffer.position(mBufferInfo.offset);
        outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
    }


    ////////////////////////////////FOR DEGBUG/////////////////////////////
    if (muxerNotStarted && outputHasChanged) {
        //set up track
        mTrackIndex = mMuxer.addTrack(outputFormat);

        mMuxer.start();
        muxerNotStarted = false;
    }

    if (!muxerNotStarted) {
        mMuxer.writeSampleData(mTrackIndex, outputBuffer, mBufferInfo);
    }
    ////////////////////////////END DEBUG//////////////////////////////////

    //release the buffer
    mCodec.releaseOutputBuffer(outputBufferIndex, false);
    muxerPasses++;
}

如果您在此走到今天你是一个绅士(或淑女!)和一个书生!基本上我难倒,为什么我的图像无法正常出来。我是比较新的视频处理,所以我敢肯定,我只是失去了一些东西。

android mediacodec color-space mediamuxer
1个回答
2
投票

如果你是API 19+,还不如用编码方法#2,getImage()/encodeVideoFromImage()坚持,因为这是更现代。

着眼于这种方法:一个问题是,你有意想不到的图像格式。随着COLOR_FormatYUV420Flexible,你知道你将有8位U和V分量,但你不会提前知道他们到何处去。这就是为什么你要查询Image.Plane格式。可能是每一个设备上的不同。

在这种情况下,UV格式竟然是交织(Android设备上很常见)。如果您在使用Java和您单独提供每个阵列(U / V),用“跨越”要求(“间隔”字节之间每个样品),我相信一个阵列最终重挫另一方面,因为这些都是实际上是“直接”的ByteBuffers,以及他们打算从本机代码中使用,就像在this answer。我说明的解决方案是交织的数组复制到第三(V)平面上,并且忽略在U平面上。在本机侧,这两个平面上相互重叠在存储器彼此(除了第一个和最后一个字节),所以填充一个导致执行,以填补两者。

如果使用第二(U)代替飞机,你会发现工作上的事情,但颜色看起来很有趣。这是因为这两个平面的重叠布置的也;是什么一样,有效地,是由一个字节的每个数组元素移位(这使将U,其中V的应该是,反之亦然。)

......换句话说,这种解决方案实际上是一个黑客攻击的一位。也许正确做到这一点,有它在所有设备上工作的唯一方式,就是使用原生代码(如我上面链接的答案)。

一旦颜色平面问题是固定的,离开所有的滑稽重叠的文本和垂直条纹。这些实际上是由您的RGB数据的解释,其中有错误的步幅造成的。

而且,一旦是固定的,你有一个体面的前瞻性图片。它被垂直镜像;我不知道的根本原因,但我怀疑这是一个OpenGL问题。

© www.soinside.com 2019 - 2024. All rights reserved.