Как получить изображение кадра при сжатии видео с MediaCode c и поверхностью - PullRequest
1 голос
/ 20 марта 2020

Я пытаюсь сжать видео с помощью медиа-кода c и сохранить исходный кадр в jpg. Я настраиваю медиакод c с поверхностью, мой код похож на коды ниже. Я хочу сохранить кадр в файл jpg после 'outputSurface.drawImage ();'. Я новичок в OpenGLES, поэтому я не знаю, как сохранить изображение из текстуры. Есть идеи?

private void doEncodeDecodeVideoFromSurfaceToSurface(MediaCodec encoder,
        InputSurface inputSurface, int encoderColorFormat, MediaCodec decoder,
        OutputSurface outputSurface) {
    final int TIMEOUT_USEC = 10000;
    ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
    ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    int generateIndex = 0;
    int checkIndex = 0;
    int badFrames = 0;
    // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
    // stream, not a .mp4 file, so not all players will know what to do with it.
    FileOutputStream outputStream = null;
    if (DEBUG_SAVE_FILE) {
        String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
        try {
            outputStream = new FileOutputStream(fileName);
            Log.d(TAG, "encoded output will be saved as " + fileName);
        } catch (IOException ioe) {
            Log.w(TAG, "Unable to create debug output file " + fileName);
            throw new RuntimeException(ioe);
        }
    }
    // Loop until the output side is done.
    boolean inputDone = false;
    boolean encoderDone = false;
    boolean outputDone = false;
    while (!outputDone) {
        if (VERBOSE) Log.d(TAG, "loop");
        // If we're not done submitting frames, generate a new one and submit it.  The
        // eglSwapBuffers call will block if the input is full.
        if (!inputDone) {
            if (generateIndex == NUM_FRAMES) {
                // Send an empty frame with the end-of-stream flag set.
                if (VERBOSE) Log.d(TAG, "signaling input EOS");
                encoder.signalEndOfInputStream();
                inputDone = true;
            } else {
                inputSurface.makeCurrent();
                generateSurfaceFrame(generateIndex);
                inputSurface.setPresentationTime(computePresentationTime(generateIndex) * 1000);
                if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
                inputSurface.swapBuffers();
            }
            generateIndex++;
        }
        // Assume output is available.  Loop until both assumptions are false.
        boolean decoderOutputAvailable = true;
        boolean encoderOutputAvailable = !encoderDone;
        while (decoderOutputAvailable || encoderOutputAvailable) {
            // Start by draining any pending output from the decoder.  It's important to
            // do this before we try to stuff any more data in.
            int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
            if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                // no output available yet
                if (VERBOSE) Log.d(TAG, "no output from decoder available");
                decoderOutputAvailable = false;
            } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
            } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // this happens before the first frame is returned
                MediaFormat decoderOutputFormat = decoder.getOutputFormat();
                if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
                        decoderOutputFormat);
            } else if (decoderStatus < 0) {
                fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
            } else {  // decoderStatus >= 0
                if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
                        " (size=" + info.size + ")");
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    if (VERBOSE) Log.d(TAG, "output EOS");
                    outputDone = true;
                }
                // The ByteBuffers are null references, but we still get a nonzero size for
                // the decoded data.
                boolean doRender = (info.size != 0);
                // As soon as we call releaseOutputBuffer, the buffer will be forwarded
                // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
                // that the texture will be available before the call returns, so we
                // need to wait for the onFrameAvailable callback to fire.  If we don't
                // wait, we risk dropping frames.
                outputSurface.makeCurrent();
                decoder.releaseOutputBuffer(decoderStatus, doRender);
                if (doRender) {
                    assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
                            info.presentationTimeUs);
                    if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
                    outputSurface.awaitNewImage();
                    outputSurface.drawImage();
                    **// TODO: Save the decoded image here!**
                    if (!checkSurfaceFrame(checkIndex++)) {
                        badFrames++;
                    }
                }
            }
            if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
                // Continue attempts to drain output.
                continue;
            }
            // Decoder is drained, check to see if we've got a new buffer of output from
            // the encoder.
            if (!encoderDone) {
                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    // no output available yet
                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
                    encoderOutputAvailable = false;
                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    // not expected for an encoder
                    encoderOutputBuffers = encoder.getOutputBuffers();
                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    // not expected for an encoder
                    MediaFormat newFormat = encoder.getOutputFormat();
                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
                } else if (encoderStatus < 0) {
                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
                } else { // encoderStatus >= 0
                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
                    if (encodedData == null) {
                        fail("encoderOutputBuffer " + encoderStatus + " was null");
                    }
                    // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
                    encodedData.position(info.offset);
                    encodedData.limit(info.offset + info.size);
                    if (outputStream != null) {
                        byte[] data = new byte[info.size];
                        encodedData.get(data);
                        encodedData.position(info.offset);
                        try {
                            outputStream.write(data);
                        } catch (IOException ioe) {
                            Log.w(TAG, "failed writing debug data to file");
                            throw new RuntimeException(ioe);
                        }
                    }
                    // Get a decoder input buffer, blocking until it's available.  We just
                    // drained the decoder output, so we expect there to be a free input
                    // buffer now or in the near future (i.e. this should never deadlock
                    // if the codec is meeting requirements).
                    //
                    // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
                    // flag set; the decoder will see this and finish configuring itself.
                    int inputBufIndex = decoder.dequeueInputBuffer(-1);
                    ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
                    inputBuf.clear();
                    inputBuf.put(encodedData);
                    decoder.queueInputBuffer(inputBufIndex, 0, info.size,
                            info.presentationTimeUs, info.flags);
                    // If everything from the encoder has been passed to the decoder, we
                    // can stop polling the encoder output.  (This just an optimization.)
                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        encoderDone = true;
                        encoderOutputAvailable = false;
                    }
                    if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
                            + (encoderDone ? " (EOS)" : ""));
                    encoder.releaseOutputBuffer(encoderStatus, false);
                }
            }
        }
    }
    if (outputStream != null) {
        try {
            outputStream.close();
        } catch (IOException ioe) {
            Log.w(TAG, "failed closing debug file");
            throw new RuntimeException(ioe);
        }
    }
    if (checkIndex != NUM_FRAMES) {
        fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
    }
    if (badFrames != 0) {
        fail("Found " + badFrames + " bad frames");
    }
}
...