Как использовать ByteBuffer в контексте MediaCodec в android

Пока я могу настроить MediaCodec для кодирования видеопотока. Цель состоит в том, чтобы сохранить созданные пользователем изображения в видеофайл.

Я использую андроидальные объекты Bitmap для работы с пользователем, чтобы вставлять кадры в поток.

См. фрагмент кода, который я использую в нижней части этого сообщения (это полный код ничего не обрезается):

MediaCodec использует ByteBuffer для обработки видео/аудиопотоков.

Растровые изображения основаны на int [], которые при преобразовании в байт [] потребуют x4 размер int []

Я провел некоторое исследование, чтобы выяснить, какие контракты существуют для ByteBuffer при работе с видео/аудиопотоками в MediaCodec, но информация почти близка к zilch.

Итак, каковы контракты на использование ByteBuffer в MediaCodec?

Определяет ли размеры рамки в MediaFormat автоматически означает, что ByteBuffers имеют ширину * высоту * 4 байта?

(я использую растровый объект за раз для каждого кадра)

Спасибо за любую помощь.

(отредактирован, добавлен код)

    import java.io.ByteArrayOutputStream;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.nio.ByteBuffer;

    import android.graphics.Rect;
    import android.graphics.Bitmap.CompressFormat;
    import android.media.MediaCodec;
    import android.media.MediaCodec.BufferInfo;
    import android.media.CamcorderProfile;
    import android.media.MediaCodecInfo;
    import android.media.MediaFormat;
    import android.util.Log;
    import android.view.View;

    public class VideoCaptureManager {

        private boolean running;

        private long presentationTime;

        public void start(View rootView, String saveFilePath){
            Log.e("OUT", saveFilePath);
            this.running = true;
            this.presentationTime = 0;
            this.capture(rootView, saveFilePath);
        }

        private void capture(final View rootView, String saveFilePath){
            if(rootView != null){
                rootView.setDrawingCacheEnabled(true);

                final Rect drawingRect = new Rect();
                rootView.getDrawingRect(drawingRect);

                try{
                    final File file = new File(saveFilePath);
                    if(file.exists()){
                        // File exists return
                        return;
                    } else {
                        File parent = file.getParentFile();
                        if(!parent.exists()){
                            parent.mkdirs();
                        }
                    }

            new Thread(){
                public void run(){
                    try{
                        DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));

                        MediaCodec codec = MediaCodec.createEncoderByType("video/mp4v-es");
                        MediaFormat mediaFormat = null;
                        if(CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)){
                            mediaFormat = MediaFormat.createVideoFormat("video/mp4v-es", 720, 1280);
                        } else {
                            mediaFormat = MediaFormat.createVideoFormat("video/mp4v-es", 480, 720);
                        }


                        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 700000);
                        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 10);
                        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
                        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();

                        while(VideoCaptureManager.this.running){
                            try{
                                int inputBufferIndex = codec.dequeueInputBuffer(-2);
                                if(inputBufferIndex >= 0){
                                    // Fill in the bitmap bytes
                                    // inputBuffers[inputBufferIndex].
                                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                                    rootView.getDrawingCache().compress(CompressFormat.JPEG, 80, baos);
                                    inputBuffers[inputBufferIndex].put(baos.toByteArray());

                                    codec.queueInputBuffer(inputBufferIndex, 0, inputBuffers[inputBufferIndex].capacity(), presentationTime, MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
                                    presentationTime += 100;
                                }

                                BufferInfo info = new BufferInfo();
                                int outputBufferIndex = codec.dequeueOutputBuffer(info, -2);
                                if(outputBufferIndex >= 0){
                                    // Write the bytes to file
                                    byte[] array = outputBuffers[outputBufferIndex].array(); // THIS THORWS AN EXCEPTION. WHAT IS THE CONTRACT TO DEAL WITH ByteBuffer in this code?
                                    if(array != null){
                                        dos.write(array);
                                    }

                                    codec.releaseOutputBuffer(outputBufferIndex, false);
                                } else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
                                    outputBuffers = codec.getOutputBuffers();
                                } else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
                                    // codec format is changed
                                    MediaFormat format = codec.getOutputFormat();
                                }

                                Thread.sleep(100);
                            }catch(Throwable th){
                                Log.e("OUT", th.getMessage(), th);
                            }
                        }

                        codec.stop();
                        codec.release();
                        codec = null;

                        dos.flush();
                        dos.close();
                    }catch(Throwable th){
                        Log.e("OUT", th.getMessage(), th);
                    }
                }
                    }.start();

                }catch(Throwable th){
                    Log.e("OUT", th.getMessage(), th);
                }
            }
        }

        public void stop(){
            this.running = false;
        }
    }

Ответ 1

Точная компоновка ByteBuffer определяется кодеком для выбранного формата ввода. Не все устройства поддерживают все возможные входные форматы (например, некоторые AVC-кодеры требуют планарного 420 YUV, другие требуют полуплоскости). Старые версии Android (< = API 17) на самом деле не предоставили переносной способ создания видеофайлов с программным обеспечением для MediaCodec.

В Android 4.3 (API 18) у вас есть два варианта. Во-первых, MediaCodec теперь принимает ввод с поверхности, что означает, что все, что вы можете сделать с помощью OpenGL ES, можно записать как фильм. См. Например, Пример EncodeAndMuxTest.

Во-вторых, у вас все еще есть возможность использовать программные буферы YUV 420, но теперь они с большей вероятностью работают, потому что есть тесты CTS, которые их используют. Вы все равно должны выполнять обнаружение времени выполнения планарной или полуплоскостной, но на самом деле есть только два макета. Для примера см. Варианты буфера-буфера EncodeDecodeTest.