Уменьшение размера видеофайла MediaMuxer (повторное сжатие, уменьшение разрешения)

Я ищу эффективный способ уменьшить вес видео (как File для загрузки), и очевидный ответ: давайте уменьшим разрешение! (fullHD или 4K не нужны, мне достаточно простого HD) Я перепробовал множество способов, которые должны работать через множество API (необходимо 10), и лучшим способом было использование android-ffmpeg-java, НО... на моем довольно быстром почти текущем флагманском устройстве весь процесс длится около length_of_video*4 секунд, а также эта библиотека весит 9 Мб, эта сумма увеличивает размер моего приложения... Нет вай! (От 12 Мб до 1 Мб - хороший результат, но все же слишком много недостатков)

Поэтому я решил использовать нативные способы Android для этого, MediaMuxer и MediaCodec - они доступны из API18 и API16 соответственно (пользователи старых устройств: извините, но они также часто имеют камеру с более низким разрешением). Метод ниже почти работает - MediaMuxer НЕ уважайте MediaFormat.KEY_WIDTH и MediaFormat.KEY_HEIGHT - извлеченное File "повторно сжимается", вес немного меньше, но разрешение такое же, как в исходном видео File...

Итак, вопрос: как сжать и масштабировать/изменить разрешение видео, используя MediaMuxer и другие сопутствующие классы и методы?

public File getCompressedFile(String videoPath) throws IOException{
    MediaExtractor extractor = new MediaExtractor();
    extractor.setDataSource(videoPath);
    int trackCount = extractor.getTrackCount();

    String filePath = videoPath.substring(0, videoPath.lastIndexOf(File.separator));
    String[] splitByDot = videoPath.split("\\.");
    String ext="";
    if(splitByDot!=null && splitByDot.length>1)
        ext = splitByDot[splitByDot.length-1];
    String fileName = videoPath.substring(videoPath.lastIndexOf(File.separator)+1,
                    videoPath.length());
    if(ext.length()>0)
        fileName=fileName.replace("."+ext, "_out."+ext);
    else
        fileName=fileName.concat("_out");

    final File outFile = new File(filePath, fileName);
    if(!outFile.exists())
        outFile.createNewFile();

    MediaMuxer muxer = new MediaMuxer(outFile.getAbsolutePath(),
            MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
    for (int i = 0; i < trackCount; i++) {
        extractor.selectTrack(i);
        MediaFormat format = extractor.getTrackFormat(i);
        String mime = format.getString(MediaFormat.KEY_MIME);
        if(mime!=null && mime.startsWith("video")){
            int currWidth = format.getInteger(MediaFormat.KEY_WIDTH);
            int currHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
            format.setInteger(MediaFormat.KEY_WIDTH, currWidth>currHeight ? 960 : 540);
            format.setInteger(MediaFormat.KEY_HEIGHT, currWidth>currHeight ? 540 : 960);
            //API19 MediaFormat.KEY_MAX_WIDTH and KEY_MAX_HEIGHT
            format.setInteger("max-width", format.getInteger(MediaFormat.KEY_WIDTH));
            format.setInteger("max-height", format.getInteger(MediaFormat.KEY_HEIGHT));
        }
        int dstIndex = muxer.addTrack(format);
        indexMap.put(i, dstIndex);
    }

    boolean sawEOS = false;
    int bufferSize = 256 * 1024;
    int offset = 100;
    ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    muxer.start();
    while (!sawEOS) {
        bufferInfo.offset = offset;
        bufferInfo.size = extractor.readSampleData(dstBuf, offset);
        if (bufferInfo.size < 0) {
            sawEOS = true;
            bufferInfo.size = 0;
        } else {
            bufferInfo.presentationTimeUs = extractor.getSampleTime();
            bufferInfo.flags = extractor.getSampleFlags();
            int trackIndex = extractor.getSampleTrackIndex();
            muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
                    bufferInfo);
            extractor.advance();
        }
    }

    muxer.stop();
    muxer.release();

    return outFile;
}

PS. много полезного о мультиплексоре здесь, код выше основан на MediaMuxerTest.java, метод cloneMediaUsingMuxer


person snachmsm    schedule 29.04.2015    source источник


Ответы (5)


Основываясь на bigflake.com/mediacodec/ (отличный источник знаний о медиа-классах), я пробовал несколько способы и, наконец, ExtractDecodeEditEncodeMuxTest оказался очень полезным. Этот тест не был описан в статье на сайте bigflake, но его можно найти ЗДЕСЬ рядом с другими классами, упомянутыми в тексте.

Итак, я скопировал большую часть кода из вышеупомянутого класса ExtractDecodeEditEncodeMuxTest, и вот он: VideoResolutionChanger. Это дает мне 2Mb HD видео из 16 Mb fullHD. Хороший! И быстро! На моем устройстве весь процесс немного длиннее, чем продолжительность входного видео, например. 10 секунд видеовхода -> 11-12 секунд обработки. С ffmpeg-java это будет что-то около 40 секунд или больше (и еще 9 МБ для приложения).

Вот так:

Видеоресолютиончейнджер:

@TargetApi(18)
public class VideoResolutionChanger {

    private static final int TIMEOUT_USEC = 10000;

    private static final String OUTPUT_VIDEO_MIME_TYPE = "video/avc";
    private static final int OUTPUT_VIDEO_BIT_RATE = 2048 * 1024;
    private static final int OUTPUT_VIDEO_FRAME_RATE = 30;
    private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10;
    private static final int OUTPUT_VIDEO_COLOR_FORMAT =
            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;

    private static final String OUTPUT_AUDIO_MIME_TYPE = "audio/mp4a-latm";
    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2;
    private static final int OUTPUT_AUDIO_BIT_RATE = 128 * 1024;
    private static final int OUTPUT_AUDIO_AAC_PROFILE =
            MediaCodecInfo.CodecProfileLevel.AACObjectHE;
    private static final int OUTPUT_AUDIO_SAMPLE_RATE_HZ = 44100;

    private int mWidth = 1280;
    private int mHeight = 720;
    private String mOutputFile, mInputFile;

    public String changeResolution(File f)
            throws Throwable {
        mInputFile=f.getAbsolutePath();

        String filePath = mInputFile.substring(0, mInputFile.lastIndexOf(File.separator));
        String[] splitByDot = mInputFile.split("\\.");
        String ext="";
        if(splitByDot!=null && splitByDot.length>1)
            ext = splitByDot[splitByDot.length-1];
        String fileName = mInputFile.substring(mInputFile.lastIndexOf(File.separator)+1,
                mInputFile.length());
        if(ext.length()>0)
            fileName=fileName.replace("."+ext, "_out.mp4");
        else
            fileName=fileName.concat("_out.mp4");

        final File outFile = new File(Environment.getExternalStorageDirectory(), fileName);
        if(!outFile.exists())
            outFile.createNewFile();

        mOutputFile=outFile.getAbsolutePath();

        ChangerWrapper.changeResolutionInSeparatedThread(this);

        return mOutputFile;
    }

    private static class ChangerWrapper implements Runnable {

        private Throwable mThrowable;
        private VideoResolutionChanger mChanger;

        private ChangerWrapper(VideoResolutionChanger changer) {
            mChanger = changer;
        }

        @Override
        public void run() {
            try {
                mChanger.prepareAndChangeResolution();
            } catch (Throwable th) {
                mThrowable = th;
            }
        }

        public static void changeResolutionInSeparatedThread(VideoResolutionChanger changer)
                throws Throwable {
            ChangerWrapper wrapper = new ChangerWrapper(changer);
            Thread th = new Thread(wrapper, ChangerWrapper.class.getSimpleName());
            th.start();
            th.join();
            if (wrapper.mThrowable != null)
                throw wrapper.mThrowable;
        }
    }

    private void prepareAndChangeResolution() throws Exception {
        Exception exception = null;

        MediaCodecInfo videoCodecInfo = selectCodec(OUTPUT_VIDEO_MIME_TYPE);
        if (videoCodecInfo == null)
            return;
        MediaCodecInfo audioCodecInfo = selectCodec(OUTPUT_AUDIO_MIME_TYPE);
        if (audioCodecInfo == null)
            return;

        MediaExtractor videoExtractor = null;
        MediaExtractor audioExtractor = null;
        OutputSurface outputSurface = null;
        MediaCodec videoDecoder = null;
        MediaCodec audioDecoder = null;
        MediaCodec videoEncoder = null;
        MediaCodec audioEncoder = null;
        MediaMuxer muxer = null;
        InputSurface inputSurface = null;
        try {
            videoExtractor = createExtractor();
            int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
            MediaFormat inputFormat = videoExtractor.getTrackFormat(videoInputTrack);

            MediaMetadataRetriever m = new MediaMetadataRetriever();
            m.setDataSource(mInputFile);
            int inputWidth, inputHeight;
            try {
                inputWidth = Integer.parseInt(m.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
                inputHeight = Integer.parseInt(m.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
            } catch (Exception e) {
                Bitmap thumbnail = m.getFrameAtTime();
                inputWidth = thumbnail.getWidth();
                inputHeight = thumbnail.getHeight();
                thumbnail.recycle();
            }

            if(inputWidth>inputHeight){
                if(mWidth<mHeight){
                    int w = mWidth;
                    mWidth=mHeight;
                    mHeight=w;
                }
            }
            else{
                if(mWidth>mHeight){
                    int w = mWidth;
                    mWidth=mHeight;
                    mHeight=w;
                }
            }

            MediaFormat outputVideoFormat =
                    MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
            outputVideoFormat.setInteger(
                    MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
            outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
            outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
            outputVideoFormat.setInteger(
                    MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);

            AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
            videoEncoder = createVideoEncoder(
                    videoCodecInfo, outputVideoFormat, inputSurfaceReference);
            inputSurface = new InputSurface(inputSurfaceReference.get());
            inputSurface.makeCurrent();

            outputSurface = new OutputSurface();
            videoDecoder = createVideoDecoder(inputFormat, outputSurface.getSurface());

            audioExtractor = createExtractor();
            int audioInputTrack = getAndSelectAudioTrackIndex(audioExtractor);
            MediaFormat inputAudioFormat = audioExtractor.getTrackFormat(audioInputTrack);
            MediaFormat outputAudioFormat =
                MediaFormat.createAudioFormat(inputAudioFormat.getString(MediaFormat.KEY_MIME),
                        inputAudioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
                        inputAudioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
            outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
            outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);

            audioEncoder = createAudioEncoder(audioCodecInfo, outputAudioFormat);
            audioDecoder = createAudioDecoder(inputAudioFormat);

            muxer = new MediaMuxer(mOutputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

            changeResolution(videoExtractor, audioExtractor,
                    videoDecoder, videoEncoder,
                    audioDecoder, audioEncoder,
                    muxer, inputSurface, outputSurface);
        } finally {
            try {
                if (videoExtractor != null)
                    videoExtractor.release();
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (audioExtractor != null)
                    audioExtractor.release();
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (videoDecoder != null) {
                    videoDecoder.stop();
                    videoDecoder.release();
                }
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (outputSurface != null) {
                    outputSurface.release();
                }
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (videoEncoder != null) {
                    videoEncoder.stop();
                    videoEncoder.release();
                }
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (audioDecoder != null) {
                    audioDecoder.stop();
                    audioDecoder.release();
                }
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (audioEncoder != null) {
                    audioEncoder.stop();
                    audioEncoder.release();
                }
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (muxer != null) {
                    muxer.stop();
                    muxer.release();
                }
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
            try {
                if (inputSurface != null)
                    inputSurface.release();
            } catch(Exception e) {
                if (exception == null)
                    exception = e;
            }
        }
        if (exception != null)
            throw exception;
    }

    private MediaExtractor createExtractor() throws IOException {
        MediaExtractor extractor;
        extractor = new MediaExtractor();
        extractor.setDataSource(mInputFile);
        return extractor;
    }

    private MediaCodec createVideoDecoder(MediaFormat inputFormat, Surface surface) throws IOException {
        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
        decoder.configure(inputFormat, surface, null, 0);
        decoder.start();
        return decoder;
    }

    private MediaCodec createVideoEncoder(MediaCodecInfo codecInfo, MediaFormat format,
            AtomicReference<Surface> surfaceReference) throws IOException {
        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        surfaceReference.set(encoder.createInputSurface());
        encoder.start();
        return encoder;
    }

    private MediaCodec createAudioDecoder(MediaFormat inputFormat) throws IOException {
        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
        decoder.configure(inputFormat, null, null, 0);
        decoder.start();
        return decoder;
    }

    private MediaCodec createAudioEncoder(MediaCodecInfo codecInfo, MediaFormat format) throws IOException {
        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        encoder.start();
        return encoder;
    }

    private int getAndSelectVideoTrackIndex(MediaExtractor extractor) {
        for (int index = 0; index < extractor.getTrackCount(); ++index) {
            if (isVideoFormat(extractor.getTrackFormat(index))) {
                extractor.selectTrack(index);
                return index;
            }
        }
        return -1;
    }
    private int getAndSelectAudioTrackIndex(MediaExtractor extractor) {
        for (int index = 0; index < extractor.getTrackCount(); ++index) {
            if (isAudioFormat(extractor.getTrackFormat(index))) {
                extractor.selectTrack(index);
                return index;
            }
        }
        return -1;
    }

    private void changeResolution(MediaExtractor videoExtractor, MediaExtractor audioExtractor,
            MediaCodec videoDecoder, MediaCodec videoEncoder,
            MediaCodec audioDecoder, MediaCodec audioEncoder,
            MediaMuxer muxer,
            InputSurface inputSurface, OutputSurface outputSurface) {
        ByteBuffer[] videoDecoderInputBuffers = null;
        ByteBuffer[] videoDecoderOutputBuffers = null;
        ByteBuffer[] videoEncoderOutputBuffers = null;
        MediaCodec.BufferInfo videoDecoderOutputBufferInfo = null;
        MediaCodec.BufferInfo videoEncoderOutputBufferInfo = null;

        videoDecoderInputBuffers = videoDecoder.getInputBuffers();
        videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
        videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
        videoDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
        videoEncoderOutputBufferInfo = new MediaCodec.BufferInfo();

        ByteBuffer[] audioDecoderInputBuffers = null;
        ByteBuffer[] audioDecoderOutputBuffers = null;
        ByteBuffer[] audioEncoderInputBuffers = null;
        ByteBuffer[] audioEncoderOutputBuffers = null;
        MediaCodec.BufferInfo audioDecoderOutputBufferInfo = null;
        MediaCodec.BufferInfo audioEncoderOutputBufferInfo = null;

        audioDecoderInputBuffers = audioDecoder.getInputBuffers();
        audioDecoderOutputBuffers =  audioDecoder.getOutputBuffers();
        audioEncoderInputBuffers = audioEncoder.getInputBuffers();
        audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
        audioDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
        audioEncoderOutputBufferInfo = new MediaCodec.BufferInfo();

        MediaFormat decoderOutputVideoFormat = null;
        MediaFormat decoderOutputAudioFormat = null;
        MediaFormat encoderOutputVideoFormat = null;
        MediaFormat encoderOutputAudioFormat = null;
        int outputVideoTrack = -1;
        int outputAudioTrack = -1;

        boolean videoExtractorDone = false;
        boolean videoDecoderDone = false;
        boolean videoEncoderDone = false;

        boolean audioExtractorDone = false;
        boolean audioDecoderDone = false;
        boolean audioEncoderDone = false;

        int pendingAudioDecoderOutputBufferIndex = -1;
        boolean muxing = false;
        while ((!videoEncoderDone) || (!audioEncoderDone)) {
            while (!videoExtractorDone
                    && (encoderOutputVideoFormat == null || muxing)) {
                int decoderInputBufferIndex = videoDecoder.dequeueInputBuffer(TIMEOUT_USEC);
                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
                    break;

                ByteBuffer decoderInputBuffer = videoDecoderInputBuffers[decoderInputBufferIndex];
                int size = videoExtractor.readSampleData(decoderInputBuffer, 0);
                long presentationTime = videoExtractor.getSampleTime();

                if (size >= 0) {
                    videoDecoder.queueInputBuffer(
                            decoderInputBufferIndex,
                            0,
                            size,
                            presentationTime,
                            videoExtractor.getSampleFlags());
                }
                videoExtractorDone = !videoExtractor.advance();
                if (videoExtractorDone)
                    videoDecoder.queueInputBuffer(decoderInputBufferIndex,
                            0, 0, 0,  MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                break;
            }

            while (!audioExtractorDone
                    && (encoderOutputAudioFormat == null || muxing)) {
                int decoderInputBufferIndex = audioDecoder.dequeueInputBuffer(TIMEOUT_USEC);
                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
                    break;

                ByteBuffer decoderInputBuffer = audioDecoderInputBuffers[decoderInputBufferIndex];
                int size = audioExtractor.readSampleData(decoderInputBuffer, 0);
                long presentationTime = audioExtractor.getSampleTime();

                if (size >= 0)
                    audioDecoder.queueInputBuffer(decoderInputBufferIndex, 0, size,
                            presentationTime, audioExtractor.getSampleFlags());

                audioExtractorDone = !audioExtractor.advance();
                if (audioExtractorDone)
                    audioDecoder.queueInputBuffer(decoderInputBufferIndex, 0, 0,
                            0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);

                break;
            }

            while (!videoDecoderDone
                    && (encoderOutputVideoFormat == null || muxing)) {
                int decoderOutputBufferIndex =
                        videoDecoder.dequeueOutputBuffer(
                                videoDecoderOutputBufferInfo, TIMEOUT_USEC);
                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
                    break;

                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
                    break;
                }
                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    decoderOutputVideoFormat = videoDecoder.getOutputFormat();
                    break;
                }

                ByteBuffer decoderOutputBuffer =
                        videoDecoderOutputBuffers[decoderOutputBufferIndex];
                if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                        != 0) {
                    videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
                    break;
                }

                boolean render = videoDecoderOutputBufferInfo.size != 0;
                videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, render);
                if (render) {
                    outputSurface.awaitNewImage();
                    outputSurface.drawImage();
                    inputSurface.setPresentationTime(
                            videoDecoderOutputBufferInfo.presentationTimeUs * 1000);
                    inputSurface.swapBuffers();
                }
                if ((videoDecoderOutputBufferInfo.flags
                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    videoDecoderDone = true;
                    videoEncoder.signalEndOfInputStream();
                }
                break;
            }

            while (!audioDecoderDone && pendingAudioDecoderOutputBufferIndex == -1
                    && (encoderOutputAudioFormat == null || muxing)) {
                int decoderOutputBufferIndex =
                        audioDecoder.dequeueOutputBuffer(
                                audioDecoderOutputBufferInfo, TIMEOUT_USEC);
                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
                    break;

                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    audioDecoderOutputBuffers = audioDecoder.getOutputBuffers();
                    break;
                }
                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    decoderOutputAudioFormat = audioDecoder.getOutputFormat();
                    break;
                }
                ByteBuffer decoderOutputBuffer =
                        audioDecoderOutputBuffers[decoderOutputBufferIndex];
                if ((audioDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                        != 0) {
                    audioDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
                    break;
                }
                pendingAudioDecoderOutputBufferIndex = decoderOutputBufferIndex;
                break;
            }

            while (pendingAudioDecoderOutputBufferIndex != -1) {
                int encoderInputBufferIndex = audioEncoder.dequeueInputBuffer(TIMEOUT_USEC);
                ByteBuffer encoderInputBuffer = audioEncoderInputBuffers[encoderInputBufferIndex];
                int size = audioDecoderOutputBufferInfo.size;
                long presentationTime = audioDecoderOutputBufferInfo.presentationTimeUs;

                if (size >= 0) {
                    ByteBuffer decoderOutputBuffer =
                            audioDecoderOutputBuffers[pendingAudioDecoderOutputBufferIndex]
                                    .duplicate();
                    decoderOutputBuffer.position(audioDecoderOutputBufferInfo.offset);
                    decoderOutputBuffer.limit(audioDecoderOutputBufferInfo.offset + size);
                    encoderInputBuffer.position(0);
                    encoderInputBuffer.put(decoderOutputBuffer);
                    audioEncoder.queueInputBuffer(
                            encoderInputBufferIndex,
                            0,
                            size,
                            presentationTime,
                            audioDecoderOutputBufferInfo.flags);
                }
                audioDecoder.releaseOutputBuffer(pendingAudioDecoderOutputBufferIndex, false);
                pendingAudioDecoderOutputBufferIndex = -1;
                if ((audioDecoderOutputBufferInfo.flags
                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)
                    audioDecoderDone = true;

                break;
            }

            while (!videoEncoderDone
                    && (encoderOutputVideoFormat == null || muxing)) {
                int encoderOutputBufferIndex = videoEncoder.dequeueOutputBuffer(
                        videoEncoderOutputBufferInfo, TIMEOUT_USEC);
                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
                    break;
                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
                    break;
                }
                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    encoderOutputVideoFormat = videoEncoder.getOutputFormat();
                    break;
                }

                ByteBuffer encoderOutputBuffer =
                        videoEncoderOutputBuffers[encoderOutputBufferIndex];
                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                        != 0) {
                    videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
                    break;
                }
                if (videoEncoderOutputBufferInfo.size != 0) {
                    muxer.writeSampleData(
                            outputVideoTrack, encoderOutputBuffer, videoEncoderOutputBufferInfo);
                }
                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                        != 0) {
                    videoEncoderDone = true;
                }
                videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
                break;
            }

            while (!audioEncoderDone
                    && (encoderOutputAudioFormat == null || muxing)) {
                int encoderOutputBufferIndex = audioEncoder.dequeueOutputBuffer(
                        audioEncoderOutputBufferInfo, TIMEOUT_USEC);
                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    break;
                }
                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
                    break;
                }
                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    encoderOutputAudioFormat = audioEncoder.getOutputFormat();
                    break;
                }

                ByteBuffer encoderOutputBuffer =
                        audioEncoderOutputBuffers[encoderOutputBufferIndex];
                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                        != 0) {
                    audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
                    break;
                }
                if (audioEncoderOutputBufferInfo.size != 0)
                    muxer.writeSampleData(
                            outputAudioTrack, encoderOutputBuffer, audioEncoderOutputBufferInfo);
                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                        != 0)
                    audioEncoderDone = true;

                audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);

                break;
            }
            if (!muxing && (encoderOutputAudioFormat != null)
                    && (encoderOutputVideoFormat != null)) {
                outputVideoTrack = muxer.addTrack(encoderOutputVideoFormat);
                outputAudioTrack = muxer.addTrack(encoderOutputAudioFormat);
                muxer.start();
                muxing = true;
            }
        }
    }

    private static boolean isVideoFormat(MediaFormat format) {
        return getMimeTypeFor(format).startsWith("video/");
    }
    private static boolean isAudioFormat(MediaFormat format) {
        return getMimeTypeFor(format).startsWith("audio/");
    }
    private static String getMimeTypeFor(MediaFormat format) {
        return format.getString(MediaFormat.KEY_MIME);
    }

    private static MediaCodecInfo selectCodec(String mimeType) {
        int numCodecs = MediaCodecList.getCodecCount();
        for (int i = 0; i < numCodecs; i++) {
            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
            if (!codecInfo.isEncoder()) {
                continue;
            }
            String[] types = codecInfo.getSupportedTypes();
            for (int j = 0; j < types.length; j++) {
                if (types[j].equalsIgnoreCase(mimeType)) {
                    return codecInfo;
                }
            }
        }
        return null;
    }
}

ему также нужны InputSurface, OutputSurface и TextureRender, которые располагаются рядом с ExtractDecodeEditEncodeMuxTest (над ЗДЕСЬ ссылка). Поместите эти три в один пакет с VideoResolutionChanger и используйте его следующим образом:

try{
    String pathToReEncodedFile =
        new VideoResolutionChanger().changeResolution(videoFilePath);
}catch(Throwable t){/* smth wrong :( */}

Где videoFilePath можно получить из File с помощью file.getAbsolutePath().

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

ОГРАНИЧЕНИЯ:

  • приведенный выше фрагмент use-it-like-this не может запускаться в основном потоке цикла (ui), например. прямо внутри Activity. лучше всего создать IntentService и пропустите путь к входному файлу String в Intents дополнительных Bundle. Затем вы можете запустить changeResolution прямо внутри onHandleIntent;
  • API18 и выше (введено MediaMuxer);
  • API18, конечно, нуждается в WRITE_EXTERNAL_STORAGE, в API19 и выше это встроено;

@fadden СПАСИБО за вашу работу и поддержку! :)

person snachmsm    schedule 30.04.2015
comment
Привет @snachmsm, спасибо за замечательный ответ. У меня возникла проблема при использовании этого кода. Я получаю исключение surface wait timed out каждый раз (независимо от того, какое время Timeout_wait я установил), и сразу после этого исключения я получаю new frame available, но к тому времени код перестал работать. Есть ли что-то, что я упускаю? - person Chetan Gawai; 21.05.2015
comment
@Chetan, возможно, [stackoverflow.com/questions/22457623/ ответ) поможет вам (извините за небольшую задержку...) - person snachmsm; 08.06.2015
comment
Отличный рабочий пример, хотя я столкнулся с интересной вещью - при касании чего-либо в параметрах аудиокодека - например, изменении аудиоканалов со 2 на 1 - закодированное видео воспроизводится на половинной скорости (аудио+видео в замедленном темпе.. .) - Вы можете объяснить? (На самом деле, даже когда ничего не менялось, я заметил, что это произошло, поскольку исходная частота дискретизации звука составляла 48 кГц, а частота дискретизации закодированного звука - 44,1 кГц, видео было немного замедлено (± 10%) ) - person Nati Dykstein; 19.08.2015
comment
если честно, я понятия не имею, очень странно... явно есть связь между этими параметрами и скоростью видео, но я не вижу в коде каких-либо комбинированных методов, которые могут вызвать такое поведение :/ - person snachmsm; 20.08.2015
comment
Похоже, вам нужно выбрать частоту кадров из недавно закодированного видео. По умолчанию это 30 кадров в секунду. Это приводит к тому, что некоторые более медленные видео с переменной частотой кадров становятся прерывистыми или воспроизводятся с более высокой скоростью из-за того, что более высокая частота кадров (30 кадров в секунду) применяется к видео со средним значением 25 кадров в секунду. Есть ли в любом случае, что вы нашли, чтобы справиться с этим? - person Michael Garner; 02.12.2015
comment
извините, я больше не исследовал этот код, понятия не имею ... Я передал эту тему коллеге вскоре после публикации выше и не участвовал в - person snachmsm; 02.12.2015
comment
@Nati Dykstein - извините за задержку, случайно я обнаружил небольшую ошибку в мультиплексировании аудио, которая может быть применена к приведенному выше коду. Я изменил одно творение MediaFormat outputAudioFormat = ... - используя те же параметры, что и для ввода (приведенный выше код предназначен только для изменения размера видео для уменьшения размера файла) - person snachmsm; 11.01.2016
comment
Я получаю только звук после использования этого... любые способы решить эту проблему - person Praveen Mulchandani; 21.02.2016
comment
Я получаю эту проблему android.media.MediaCodec$CodecException: ошибка 0x80001001 здесь encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); в методе createAudioEncoder() - person Roshni jain; 04.02.2017
comment
Вы проверили ЭТОТ вопрос? Я проверю эту ошибку и отредактирую свой ответ (на следующей неделе). Похоже, это редкая ошибка на некоторых устройствах... Ура! - person snachmsm; 05.02.2017
comment
@snachmsm У меня также есть такой же surface wait timed out в файле OutputSurface, можете ли вы мне что-нибудь предложить? - person Vishal Patel; 29.08.2017
comment
Работает отлично. Ради полноты, как бы вы реализовали progressStatus для обновления потока пользовательского интерфейса? Спасибо - person micaball; 22.08.2019
comment
В настоящее время я не работаю с этим классом в своей работе, но я знаю, что коллега, ответственный за эту часть кода, также планирует добавить эту функцию (progres), но после некоторой проверки он сказал, что это сложно и имеет низкий приоритет , так что не так быстро... Извините, в настоящее время я не могу вам помочь - person snachmsm; 24.08.2019

MediaMuxer не участвует в сжатии или масштабировании видео. Все, что он делает, это берет вывод H.264 из MediaCodec и оборачивает его в файловую оболочку .mp4.

Глядя на свой код, вы извлекаете блоки NAL с помощью MediaExtractor и сразу же перепаковываете их с помощью MediaMuxer. Это должно быть очень быстро и не влиять на само видео, так как вы просто перепаковываете H.264.

Для масштабирования видео необходимо декодировать видео декодером MediaCodec, подавая в него блоки NAL из MediaExtractor, и перекодировать его кодером MediaCodec, передавая кадры в MediaMuxer.

Вы нашли bigflake.com; см. также График. Ни в одном из них нет именно того, что вы ищете, но есть различные части.

Лучше всего декодировать на поверхность, а не на байтовый буфер. Для этого требуется API 18, но для здравого смысла лучше забыть, что MediaCodec существовал до этого. И вам в любом случае понадобится API 18 для MediaMuxer.

person fadden    schedule 29.04.2015
comment
спасибо, я прочитал некоторые документы и тесты и сделал то, что искал. ваши комментарии и ответы на различные вопросы о стеке были очень полезны! Я использовал один из тестов и Surface с несколькими кодировщиками и декодерами :) - person snachmsm; 30.04.2015

Я не буду возражать против проблем с реализацией и кодированием вопроса. Но мы пережили ту же катастрофу, поскольку ffmpeg увеличил размер нашего приложения как минимум на 19 МБ, и я использовал этот вопрос о стеке, чтобы придумать библиотеку, которая делает то же самое без ffmpeg. Судя по всему, ребята из linkedin уже делали это раньше. Прочтите эту статью.

Проект называется LiTr и доступен на github. Он использует Android MediaCodec и MediaMuxer, поэтому вы можете обращаться к кодам, чтобы получить помощь в своих собственных проектах, если вам это нужно. Этот вопрос был задан 4 года назад, но я надеюсь, что это поможет кому-то сейчас.

person Sep GH    schedule 07.04.2020

Вы можете попробовать Intel INDE Media for Mobile, учебные пособия на https://software.intel.com/en-us/articles/intel-inde-media-pack-for-android-tutorials. В нем есть пример, который показывает, как использовать его для перекодирования = повторного сжатия видеофайлов.

Вы можете установить меньшее разрешение и/или битрейт для вывода, чтобы получить файл меньшего размера https://github.com/INDExOS/media-for-mobile/blob/master/Android/samples/apps/src/com/intel/inde/mp/samples/ComposerTranscodeCoreActivity.java

person Marlon    schedule 29.04.2015
comment
проверил это, но INDE не поддерживает MPEG4 см. отмеченный ответ здесь - person snachmsm; 29.04.2015

Используйте компиляцию 'com.zolad:videoslimmer:1.0.0'

person Vibhu Vikram Singh    schedule 30.08.2020