16

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

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

但是,我对此进行了很多测试,最终得到以下两种情况之一:

  • 我尝试模仿的所有示例项目都导致媒体服务器queueInputBuffer在第二次调用时死机。
  • 我尝试在最后调用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?

注意:我知道MediaMux在 Android 4.3 中,但是,这不是我的选择,因为我的应用程序必须在 Android 4.1+ 上运行。

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

视频输出

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

4

1 回答 1

12

我认为您的总体思路是正确的。需要注意的一些事项:

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

编解码器的输出通常非常“原始”,例如 AVC 编解码器发出 H.264 基本流而不是“熟”的 .mp4 文件。许多玩家不会接受这种格式。如果你不能依赖你的存在,MediaMuxer你将需要找到另一种方法来处理数据(在 stackoverflow 上搜索想法)。

媒体服务器进程当然不会崩溃。

您可以在此处找到一些示例和指向 4.3 CTS 测试的链接。

更新:从 Android 4.3 开始,MediaCodec没有Camera共同的 ByteBuffer 格式,所以至少你需要摆弄色度平面。但是,这类问题的表现形式非常不同(如本问题的图像所示)。

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

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

于 2013-09-13T22:54:22.533 回答