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

Сжатие видео на андроид с использованием новой библиотеки 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 всего несколькими примерами кода для декодирования... Большое спасибо за помощь

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

Спасибо за ваше решение, но FFMPEG не работает с Android версии Nougat 7.1.1 и выше.