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

Видео выход

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

Solutions Collecting From Web of "Использование MediaCodec для сохранения серии изображений в виде видео"

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

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