Сжатие видео на Android с использованием новой библиотеки MediaCodec

В моем приложении я пытаюсь загрузить несколько видео, которые пользователь выбрал из галереи. Проблема в том, что обычно файлы видеороликов для Android очень велики для загрузки, и поэтому мы хотим сжать их сначала за счет более низкого битрейта / разрешения.

Я только что слышал о новом MediaCodec api, который представлен с API 16 (я пробовал это делать с помощью ffmpeg).

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

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

Похоже, декодирование в порядке, потому что я проверяю его, отображая его на поверхности. Сначала я сконфигурировал декодер для работы с Surface, и когда мы вызываем releaseOutputBuffer, мы используем флаг render, и мы можем видеть видео на экране.

Вот код, который я использую:

//init decoder MediaCodec decoder = MediaCodec.createDecoderByType(mime); decoder.configure(format, null , null , 0); decoder.start(); ByteBuffer[] codecInputBuffers = decoder.getInputBuffers(); ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers(); //init encoder MediaCodec encoder = MediaCodec.createEncoderByType(mime); int width = format.getInteger(MediaFormat.KEY_WIDTH); int height = format.getInteger(MediaFormat.KEY_HEIGHT); MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 400000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); encoder.configure(mediaFormat, null , null , MediaCodec.CONFIGURE_FLAG_ENCODE); encoder.start(); ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers(); ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); extractor.selectTrack(0); boolean sawInputEOS = false; boolean sawOutputEOS = false; boolean sawOutputEOS2 = false; MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); BufferInfo encoderInfo = new MediaCodec.BufferInfo(); while (!sawInputEOS || !sawOutputEOS || !sawOutputEOS2) { if (!sawInputEOS) { sawInputEOS = decodeInput(extractor, decoder, codecInputBuffers); } if (!sawOutputEOS) { int outputBufIndex = decoder.dequeueOutputBuffer(info, 0); if (outputBufIndex >= 0) { sawOutputEOS = decodeEncode(extractor, decoder, encoder, codecOutputBuffers, encoderInputBuffers, info, outputBufIndex); } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { Log.d(LOG_TAG, "decoding INFO_OUTPUT_BUFFERS_CHANGED"); codecOutputBuffers = decoder.getOutputBuffers(); } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { final MediaFormat oformat = decoder.getOutputFormat(); Log.d(LOG_TAG, "decoding Output format has changed to " + oformat); } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { Log.d(LOG_TAG, "decoding dequeueOutputBuffer timed out!"); } } if (!sawOutputEOS2) { int encodingOutputBufferIndex = encoder.dequeueOutputBuffer(encoderInfo, 0); if (encodingOutputBufferIndex >= 0) { sawOutputEOS2 = encodeOuput(outputStream, encoder, encoderOutputBuffers, encoderInfo, encodingOutputBufferIndex); } else if (encodingOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { Log.d(LOG_TAG, "encoding INFO_OUTPUT_BUFFERS_CHANGED"); encoderOutputBuffers = encoder.getOutputBuffers(); } else if (encodingOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { final MediaFormat oformat = encoder.getOutputFormat(); Log.d(LOG_TAG, "encoding Output format has changed to " + oformat); } else if (encodingOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { Log.d(LOG_TAG, "encoding dequeueOutputBuffer timed out!"); } } } //clear some stuff here... 

И это метод, который я использую для декодирования / кодирования:

  private boolean decodeInput(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] codecInputBuffers) { boolean sawInputEOS = false; int inputBufIndex = decoder.dequeueInputBuffer(0); if (inputBufIndex >= 0) { ByteBuffer dstBuf = codecInputBuffers[inputBufIndex]; input1count++; int sampleSize = extractor.readSampleData(dstBuf, 0); long presentationTimeUs = 0; if (sampleSize < 0) { sawInputEOS = true; sampleSize = 0; Log.d(LOG_TAG, "done decoding input: #" + input1count); } else { presentationTimeUs = extractor.getSampleTime(); } decoder.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); if (!sawInputEOS) { extractor.advance(); } } return sawInputEOS; } private boolean decodeOutputToFile(MediaExtractor extractor, MediaCodec decoder, ByteBuffer[] codecOutputBuffers, MediaCodec.BufferInfo info, int outputBufIndex, OutputStream output) throws IOException { boolean sawOutputEOS = false; ByteBuffer buf = codecOutputBuffers[outputBufIndex]; if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { sawOutputEOS = true; Log.d(LOG_TAG, "done decoding output: #" + output1count); } if (info.size > 0) { output1count++; byte[] outData = new byte[info.size]; buf.get(outData); output.write(outData, 0, outData.length); } else { Log.d(LOG_TAG, "no data available " + info.size); } buf.clear(); decoder.releaseOutputBuffer(outputBufIndex, false); return sawOutputEOS; } private boolean encodeInputFromFile(MediaCodec encoder, ByteBuffer[] encoderInputBuffers, MediaCodec.BufferInfo info, FileChannel channel) throws IOException { boolean sawInputEOS = false; int inputBufIndex = encoder.dequeueInputBuffer(0); if (inputBufIndex >= 0) { ByteBuffer dstBuf = encoderInputBuffers[inputBufIndex]; input1count++; int sampleSize = channel.read(dstBuf); if (sampleSize < 0) { sawInputEOS = true; sampleSize = 0; Log.d(LOG_TAG, "done encoding input: #" + input1count); } encoder.queueInputBuffer(inputBufIndex, 0, sampleSize, channel.position(), sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); } return sawInputEOS; } 

Любое предложение о том, что я делаю неправильно?

Я не нашел слишком много примеров для кодирования с MediaCodec всего несколько примеров кода для декодирования … Большое спасибо за помощь

Solutions Collecting From Web of "Сжатие видео на Android с использованием новой библиотеки MediaCodec"

Вывод MediaCodec является сырым элементарным потоком. Вам нужно упаковать его в формат видеофайла (возможно, перелистывать аудио), прежде чем многие игроки узнают его. FWIW, я обнаружил, что основанный на GStreamer проигрыватель Totem Movie для Linux будет воспроизводить «сырые» видео / avc-файлы.

Обновление: способ конвертировать H.264 в .mp4 на Android – это класс MediaMuxer , представленный в Android 4.3 (API 18). Есть несколько примеров (EncodeAndMuxTest, CameraToMpegTest), которые демонстрируют его использование.

Я просто наткнулся на эту библиотеку, которая может помочь вам создать надлежащий контейнер, чтобы видеоплееры узнали его: http://code.google.com/p/mp4parser/

Используйте библиотеку SiliCompressor для решения этой проблемы. Compresse Video и изображения при сохранении качества носителя.