使用MediaCodec将一系列图像保存为视频

时间:2013-09-13 21:13:29

标签: android video mediacodec

我正在尝试使用MediaCodec将一系列图像(保存为文件中的字节数组)保存到视频文件中。我已经在SurfaceView上测试了这些图像(在系列中播放),我可以看到它们很好。我使用MediaCodec查看了许多示例,这是我理解的(如果我错了请纠正我):

  

从MediaCodec对象获取InputBuffers - >用你的框架填充它   图像数据 - >排队输入缓冲区 - >获得编码输出缓冲区 - >   将其写入文件 - >增加演示时间并重复

但是,我已经对此进行了大量测试,最终我遇到了两种情况之一:

  • 我试图模仿的所有示例项目都在第二次调用queueInputBuffer时导致Media服务器死亡。
  • 我尝试在最后调用codec.flush()(在将输出缓冲区保存到文件之后,虽然我看到的所有示例都没有这样做)并且媒体服务器没有死,但是,我无法打开输出任何媒体播放器的视频文件,都有问题。

这是我的代码:

MediaCodec codec = MediaCodec.createEncoderByType(MIMETYPE);
        MediaFormat mediaFormat = null;
        if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
            mediaFormat = MediaFormat.createVideoFormat(MIMETYPE, 1280 , 720);
        } else {
            mediaFormat = MediaFormat.createVideoFormat(MIMETYPE, 720, 480);
        }


        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 700000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 10);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
        codec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

        codec.start();

        ByteBuffer[] inputBuffers = codec.getInputBuffers();
        ByteBuffer[] outputBuffers = codec.getOutputBuffers();
        boolean sawInputEOS = false;
        int inputBufferIndex=-1,outputBufferIndex=-1;
        BufferInfo info=null;

                    //loop to read YUV byte array from file

            inputBufferIndex = codec.dequeueInputBuffer(WAITTIME);
            if(bytesread<=0)sawInputEOS=true;

            if(inputBufferIndex >= 0){
                if(!sawInputEOS){
                    int samplesiz=dat.length;
                    inputBuffers[inputBufferIndex].put(dat);
                    codec.queueInputBuffer(inputBufferIndex, 0, samplesiz, presentationTime, 0);
                    presentationTime += 100;

                    info = new BufferInfo();
                    outputBufferIndex = codec.dequeueOutputBuffer(info, WAITTIME);
                    Log.i("BATA", "outputBufferIndex="+outputBufferIndex);
                    if(outputBufferIndex >= 0){
                        byte[] array = new byte[info.size];
                        outputBuffers[outputBufferIndex].get(array);

                        if(array != null){
                            try {
                                dos.write(array);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                        codec.releaseOutputBuffer(outputBufferIndex, false);
                        inputBuffers[inputBufferIndex].clear();
                        outputBuffers[outputBufferIndex].clear();

                        if(sawInputEOS) break;
                    }
                }else{
                    codec.queueInputBuffer(inputBufferIndex, 0, 0, presentationTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM);

                    info = new BufferInfo();
                    outputBufferIndex = codec.dequeueOutputBuffer(info, WAITTIME);

                    if(outputBufferIndex >= 0){
                        byte[] array = new byte[info.size];
                        outputBuffers[outputBufferIndex].get(array);

                        if(array != null){
                            try {
                                dos.write(array);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                        codec.releaseOutputBuffer(outputBufferIndex, false);
                        inputBuffers[inputBufferIndex].clear();
                        outputBuffers[outputBufferIndex].clear();
                        break;
                    }
                }


            }
        }

        codec.flush();

        try {
            fstream2.close();
            dos.flush();
            dos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        codec.stop();
        codec.release();
        codec = null;

        return true;

    }

我的问题是,如何使用MediaCodec从图像流中获取工作视频。我做错了什么?

另一个问题(如果我不太贪心),我想为这个视频添加一个音频轨道,也可以用MediaCodec完成,或者我必须使用FFmpeg吗?

注意:我知道Android 4.3中的MediaMux,但是,它不适合我,因为我的应用必须适用于Android 4.1 +。

更新 感谢fadden回答,我能够在没有媒体服务器死亡的情况下到达EOS(上面的代码是在修改之后)。但是,我得到的文件正在产生胡言乱语。这是我得到的视频的快照(仅适用于.h264文件)。

Video Output

我的输入图像格式是YUV图像(来自相机预览的NV21)。我不能让它成为任何可玩的格式。我尝试了所有COLOR_FormatYUV420格式和相同的乱码输出。我仍然无法找到(使用MediaCodec)添加音频。

1 个答案:

答案 0 :(得分:11)

我认为你有正确的总体思路。有些事情需要注意:

  • 并非所有设备都支持COLOR_FormatYUV420SemiPlanar。有些人只接受平面。 (Android 4.3引入了CTS测试以确保AVC编解码器支持其中一种。)
  • 排队输入缓冲区不会立即导致生成一个输出缓冲区。某些编解码器可能会在产生输出之前累积几个输入帧,并可能在输入完成后产生输出。确保您的循环考虑到这一点(例如,如果它仍为-1,则inputBuffers[].clear()会爆炸。)
  • 请勿尝试提交数据并使用相同的queueInputBuffer电话发送EOS。可以丢弃该帧中的数据。始终使用零长度缓冲区发送EOS。

编解码器的输出通常很漂亮&#34; raw&#34 ;,例如AVC编解码器发出的是H.264基本流,而不是&#34; cooked&#34; .mp4文件。很多玩家都不接受这种格式。如果您不能依赖MediaMuxer的存在,您将需要找到另一种方法来烹饪数据(在stackoverflow上搜索想法)。

毫无疑问,mediaserver进程会崩溃。

您可以找到4.3 CTS测试here的一些示例和链接。

更新:从Android 4.3开始,MediaCodecCamera没有共同的ByteBuffer格式,所以至少你需要摆弄色度平面。但是,这种问题的表现方式却截然不同(如this question的图片所示)。

您添加的图片看起来像视频,但有大步和/或对齐问题。确保您的像素布局正确。在CTS EncodeDecodeTest中,generateFrame()方法(第906行)显示了如何为MediaCodec编码平面和半平面YUV420。

避免格式问题的最简单方法是将帧移动到Surface(如CameraToMpegTest示例),但不幸的是,在Android 4.1中无法实现。