【问题标题】:How to get bitmap (frames) from video using MediaCodec如何使用 MediaCodec 从视频中获取位图(帧)
【发布时间】:2014-06-12 20:46:36
【问题描述】:

我正在尝试使用 MediaCodec 从视频文件中获取所有帧。如果我尝试在 SurfaceView 上显示视频,一切正常。但是如果表面为空,当我尝试从字节数组中获取位图时,总是会得到空或运行时异常。

这是我的代码:

private class PlayerThread extends Thread {
    private MediaExtractor extractor;
    private MediaCodec decoder;
    private Surface surface;

    public PlayerThread(Surface surface) {
        this.surface = surface;
    }

    @Override
    public void run() {
        extractor = new MediaExtractor();
        extractor.setDataSource(videoPath);

        for (int i = 0; i < extractor.getTrackCount(); i++) {
            MediaFormat format = extractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith("video/")) {
                extractor.selectTrack(i);
                decoder = MediaCodec.createDecoderByType(mime);
                decoder.configure(format, /*surface*/ null, null, 0);
                break;
            }
        }

        if (decoder == null) {
            Log.e("DecodeActivity", "Can't find video info!");
            return;
        }

        decoder.start();

        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
        BufferInfo info = new BufferInfo();
        boolean isEOS = false;

        while (!Thread.interrupted()) {
            ++numFrames;
            if (!isEOS) {
                int inIndex = decoder.dequeueInputBuffer(10000);
                if (inIndex >= 0) {
                    ByteBuffer buffer = inputBuffers[inIndex];
                    int sampleSize = extractor.readSampleData(buffer, 0);
                    if (sampleSize < 0) {
                        // We shouldn't stop the playback at this point,
                        // just pass the EOS
                        // flag to decoder, we will get it again from the
                        // dequeueOutputBuffer
                        Log.d("DecodeActivity",
                                "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                        decoder.queueInputBuffer(inIndex, 0, 0, 0,
                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        isEOS = true;
                    } else {
                        decoder.queueInputBuffer(inIndex, 0, sampleSize,
                                extractor.getSampleTime(), 0);
                        extractor.advance();
                    }
                }
            }

            int outIndex = decoder.dequeueOutputBuffer(info, 10000);
            switch (outIndex) {
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                Log.d("DecodeActivity", "INFO_OUTPUT_BUFFERS_CHANGED");
                outputBuffers = decoder.getOutputBuffers();
                break;
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                Log.d("DecodeActivity",
                        "New format " + decoder.getOutputFormat());
                break;
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                Log.d("DecodeActivity", "dequeueOutputBuffer timed out!");
                break;
            default:
                // I tried get Bitmap on few ways
                //1.
                //ByteBuffer buffer = outputBuffers[outIndex];
                //byte[] ba = new byte[buffer.remaining()];
                //buffer.get(ba);
                //final Bitmap bmp = BitmapFactory.decodeByteArray(ba, 0, ba.length);// this return null
                //2. 
                //ByteBuffer buffer = outputBuffers[outIndex];
                //final Bitmap bmp = Bitmap.createBitmap(1920, 1080, Config.ARGB_8888);//using MediaFormat object I know width and height
                //int a = bmp.getByteCount(); //8294400
                //buffer.rewind();
                //int b = buffer.capacity();//3137536
                //int c = buffer.remaining();//3137536
                //bmp.copyPixelsFromBuffer(buffer); // java.lang.RuntimeException: Buffer not large enough for pixels
                //I know what exception mean, but i don't know why xception occurs

                //In this place I need bitmap

                // We use a very simple clock to keep the video FPS, or the
                // video
                // playback will be too fast
                while (info.presentationTimeUs / 1000 > System
                        .currentTimeMillis() - startMs) {
                    try {
                        sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
                decoder.releaseOutputBuffer(outIndex, true);
                break;
            }

            // All decoded frames have been rendered, we can stop playing
            // now
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.d("DecodeActivity",
                        "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                break;
            }
        }

        decoder.stop();
        decoder.release();
        extractor.release();
    }
}

我不知道如何解决它。

萨摩斯

【问题讨论】:

    标签: android bitmap android-mediacodec


    【解决方案1】:

    您的代码(或者,可以说是 MediaCodec)存在一些问题。

    首先,MediaCodec 中的 ByteBuffer 处理很差,因此您必须根据 dequeueOutputBuffer() 填写的 BufferInfo 对象中的值手动设置缓冲区参数。

    其次,来自 MediaCodec 的值是 YUV 格式,而不是 RGB。从 Android 4.4 开始,Android 框架不提供将输出转换为位图的功能。您需要提供自己的 YUV 到 RGB 转换器(复数 - 有多种格式)。一些设备使用专有的、未记录的颜色格式。

    您可以在 CTS EncodeDecodeTest buffer-to-buffer 方法(例如 checkFrame())中查看提取和检查 MediaCodec 解码器缓冲区内容的示例。

    解决此问题的更可靠方法是返回解码到 Surface,但使用 OpenGL ES 提取像素。 bigflake ExtractMpegFramesTest 展示了如何做到这一点。

    【讨论】:

    【解决方案2】:

    简短的回答是:

    在你的 switch 语句的默认部分,你需要重置 ByteBuffer 的位置,所以不是:

    ByteBuffer buffer = outputBuffers[outIndex];
    byte[] ba = new byte[buffer.remaining()];
    buffer.get(ba); 
    

    你应该有类似的东西

    ByteBuffer buffer = outputBuffers[outIndex];
    buffer.position(info.offset);
    buffer.limit(info.offset + info.size);
    byte[] ba = new byte[buffer.remaining()];
    buffer.get(ba); 
    

    在您的原始代码中,您会发现您的 ByteBuffer 的剩余()为 0。

    【讨论】:

      猜你喜欢
      • 2018-01-13
      • 2013-11-14
      • 2015-08-19
      • 2013-06-10
      • 2018-05-17
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多