Подтвердить что ты не робот

Использование MediaCodec для сохранения серии изображений в виде видео

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

Получить InputBuffers из объекта MediaCodec → заполнить его с помощью фрейма данные изображения → очередь входного буфера → получить кодированный выходной буфер → записать его в файл → увеличить время представления и повторить

Однако я много тестировал, и в итоге я получил один из двух случаев:

  • Все примеры проектов, которые я пытался имитировать, заставили Media Server умереть при вызове 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 без смерти сервера Media (выше код после модификации). Тем не менее, файл, который я получаю, производит тарабарщину. Вот снимок видео, которое я получаю (работает только как файл .h264).

Video Output

Мой формат входного изображения - изображение YUV (NV21 из предварительного просмотра камеры). Я не могу получить его в любом формате. Я пробовал все форматы COLOR_FormatYUV420 и один и тот же барабанный вывод. И я все еще не могу найти (используя MediaCodec) для добавления звука.

4b9b3361

Ответ 1

Я думаю, что у вас есть правильная общая идея. Некоторые вещи, о которых нужно знать:

  • Не все устройства поддерживают COLOR_FormatYUV420SemiPlanar. Некоторые принимают только план. (Android 4.3 представил тесты CTS, чтобы гарантировать, что AVC-кодек поддерживает тот или иной.)
  • Это не тот случай, когда очередь на входной буфер немедленно приводит к генерации одного выходного буфера. Некоторые кодеки могут накапливать несколько кадров ввода перед созданием вывода и могут выдавать выходные данные после завершения ввода. Убедитесь, что ваши циклы учитывают это (например, ваш inputBuffers[].clear() взорвется, если он еще -1).
  • Не пытайтесь отправлять данные и отправлять EOS с тем же вызовом queueInputBuffer. Данные в этом фрейме могут быть отброшены. Всегда отправляйте EOS с буфером нулевой длины.

Выходные кодеки обычно довольно "сырые", например. AVC-кодек испускает элементарный поток H.264, а не "приготовленный".mp4 файл. Многие игроки не будут принимать этот формат. Если вы не можете полагаться на присутствие MediaMuxer, вам нужно будет найти другой способ скомпилировать данные (искать вокруг в stackoverflow для идей).

Это, конечно, не ожидало, что процесс mediaserver потерпит крах.

Вы можете найти несколько примеров и ссылок на тесты 4.3 CTS здесь.

Обновление:. Начиная с Android 4.3, MediaCodec и Camera не имеют форматов ByteBuffer, поэтому, по крайней мере, вам нужно будет возиться с плоскостями цветности. Однако такая проблема проявляется совсем по-другому (как показано на изображениях для этого вопроса).

Добавленное изображение выглядит как видео, но с проблемами шага и/или выравнивания. Убедитесь, что ваши пиксели установлены правильно. В CTS EncodeDecodeTest метод generateFrame() (строка 906) показывает, как кодировать как плоский, так и полуплоский YUV420 для MediaCodec.

Самый простой способ избежать проблем с форматом - переместить кадры через Surface (например, образец CameraToMpegTest), но, к сожалению, это невозможно в Android 4.1.