9

我正在寻找有效的方法来减少一些视频的重量(作为File上传),显而易见的答案是:让我们降低分辨率!(不需要全高清或 4K,简单的高清对我来说就足够了)我尝试了很多应该通过很多 API 工作的方法(需要 10 个),最好的方法是使用android-ffmpeg-java,但是......在我漂亮的快速几乎当前的旗舰设备整个过程持续大约 length_of_video*4 秒,而且这个库的重量是 9 Mb,这个数量增加了我的应用程序大小......不!(12 Mb 到 1 Mb 是不错的结果,但仍然有太多缺陷)

所以我决定使用原生 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。这里有很多关于 muxer 的有用的东西,上面的代码基于MediaMuxerTest.java,方法cloneMediaUsingMuxer

4

8 回答 8

21

基于bigflake.com/mediacodec/(关于媒体类的绝佳知识来源),我尝试了几种方法,最后ExtractDecodeEditEncodeMuxTest非常有帮助。此测试未在 bigflake 站点上的文章中描述,但可以在此处在文本中提到的其他类旁边找到。

ExtractDecodeEditEncodeMuxTest所以,我已经从上面提到的类中复制了大部分代码,它是: VideoResolutionChanger. 它为我提供了 16 Mb fullHD 的 2Mb 高清视频。好的!而且快!在我的设备上,整个过程比输入视频持续时间长一点,例如 10 秒视频输入 -> 11-12 秒处理。有了ffmpeg-java它大约需要 40 秒或更多(应用程序需要 9 Mb 以上)。

开始了:

VideoResolutionChanger:

@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,OutputSurfaceTextureRender, 放在旁边ExtractDecodeEditEncodeMuxTest(在HERE链接上方)。将这三个放在同一个包中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。最好的方法是在s extra中创建IntentService传递输入文件路径。然后就可以直接往里面跑了;StringIntentBundlechangeResolutiononHandleIntent
  • API18及以上(MediaMuxer引入);
  • API18当然需要WRITE_EXTERNAL_STORAGE,API19及以上有这个“内置”;

@fadden感谢您的工作和支持!:)

于 2015-04-30T18:44:27.910 回答
5

MediaMuxer 不参与视频的压缩或缩放。它所做的只是从 MediaCodec 获取 H.264 输出并将其包装在 .mp4 文件包装器中。

查看您的代码,您正在使用 MediaExtractor 提取 NAL 单元,并立即使用 MediaMuxer 重新包装它们。这应该非常快并且对视频本身没有影响,因为您只是重新包装 H.264。

要缩放视频,您需要使用 MediaCodec 解码器解码视频,将来自 MediaExtractor 的 NAL 单元输入其中,然后使用 MediaCodec 编码器对其重新编码,将帧传递给 MediaMuxer。

你找到了 bigflake.com;另见涂鸦。这些都没有你正在寻找的东西,但各种部件都在那里。

最好解码为 Surface,而不是 ByteBuffer。这需要 API 18,但为了理智,最好忘记 MediaCodec 在此之前存在。无论如何,您都需要用于 MediaMuxer 的 API 18。

于 2015-04-29T16:04:02.310 回答
1

我不会介意这个问题的实现和编码问题。但是我们经历了同样的灾难,因为 ffmpeg 将我们的应用程序大小至少增加了 19MB,我正在使用这个 stackoverflow 问题来提出一个没有 ffmpeg 的库。显然,以前的人linkedin已经这样做了。检查这篇文章

该项目名为LiTr可在 github 上找到。它使用 android MediaCodec 和 MediaMuxer,因此如果需要,您可以参考代码以获取有关您自己的项目的帮助。这个问题是 4 年前提出的,但我希望这对现在的人有所帮助。

于 2020-04-07T11:23:59.247 回答
0

您可以尝试英特尔 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

于 2015-04-29T13:01:44.963 回答
0

使用 compile 'com.zolad:videoslimmer:1.0.0'

于 2020-08-30T06:30:37.407 回答
0

VideoResolutionChanger.kt

class VideoResolutionChanger {

private val TIMEOUT_USEC = 10000

private val OUTPUT_VIDEO_MIME_TYPE = "video/avc"
private val OUTPUT_VIDEO_BIT_RATE = 2048 * 1024
private val OUTPUT_VIDEO_FRAME_RATE = 60
private val OUTPUT_VIDEO_IFRAME_INTERVAL = 1
private val OUTPUT_VIDEO_COLOR_FORMAT = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface

private val OUTPUT_AUDIO_MIME_TYPE = "audio/mp4a-latm"
private val OUTPUT_AUDIO_CHANNEL_COUNT = 2
private val OUTPUT_AUDIO_BIT_RATE = 128 * 1024
private val OUTPUT_AUDIO_AAC_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectHE
private val OUTPUT_AUDIO_SAMPLE_RATE_HZ = 44100

private var mWidth = 1920
private var mHeight = 1080
private var mOutputFile : String? = null
private var mInputFile : String? = null

private var mTotalTime : Int = 0

@Throws(Throwable::class)

fun changeResolution(f: File): String? {

    mInputFile = f.absolutePath

    val filePath : String? = mInputFile!!.substring(0, mInputFile!!.lastIndexOf(File.separator))
    val splitByDot: Array<String> = mInputFile!!.split("\\.").toTypedArray()
    var ext = ""
    if (splitByDot.size > 1) ext = splitByDot[splitByDot.size - 1]
    var fileName: String = mInputFile!!.substring(
        mInputFile!!.lastIndexOf(File.separator) + 1,
        mInputFile!!.length
    )

    fileName = if (ext.length > 0) fileName.replace(".$ext", "_out.mp4") else fileName + "_out.mp4"

    mOutputFile = outFile.getAbsolutePath()

    ChangerWrapper.changeResolutionInSeparatedThread(this)

    return mOutputFile

}

private class ChangerWrapper private constructor(private val mChanger: VideoResolutionChanger) :
    Runnable {

    private var mThrowable : Throwable? = null

    override fun run() {

        try {

            mChanger.prepareAndChangeResolution()

        } catch (th: Throwable) {

            mThrowable = th

        }

    }

    companion object {
        @Throws(Throwable::class)

        fun changeResolutionInSeparatedThread(changer: VideoResolutionChanger) {

            val wrapper = ChangerWrapper(changer)
            val th = Thread(wrapper, ChangerWrapper::class.java.simpleName)
            th.start()
            th.join()
            if (wrapper.mThrowable != null) throw wrapper.mThrowable!!

        }

    }

}

@Throws(Exception::class)
private fun prepareAndChangeResolution() {

    var exception: Exception? = null
    val videoCodecInfo = selectCodec(OUTPUT_VIDEO_MIME_TYPE) ?: return
    val audioCodecInfo = selectCodec(OUTPUT_AUDIO_MIME_TYPE) ?: return
    var videoExtractor : MediaExtractor? = null
    var audioExtractor : MediaExtractor? = null
    var outputSurface : OutputSurface? = null
    var videoDecoder : MediaCodec? = null
    var audioDecoder : MediaCodec? = null
    var videoEncoder : MediaCodec? = null
    var audioEncoder : MediaCodec? = null
    var muxer : MediaMuxer? = null
    var inputSurface : InputSurface? = null
    try {
        videoExtractor = createExtractor()
        val videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor)
        val inputFormat = videoExtractor!!.getTrackFormat(videoInputTrack)
        val m = MediaMetadataRetriever()
        m.setDataSource(mInputFile)
        var inputWidth: Int
        var inputHeight: Int

        try {
            inputWidth =
                m.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt()
            inputHeight =
                m.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt()
            mTotalTime =
                m.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!.toInt() * 1000
        } catch (e: Exception) {
            val thumbnail = m.frameAtTime
            inputWidth = thumbnail!!.width
            inputHeight = thumbnail.height
            thumbnail.recycle()
        }
        if (inputWidth > inputHeight) {
            if (mWidth < mHeight) {
                val w = mWidth
                mWidth = mHeight
                mHeight = w
            }
        } else {
            if (mWidth > mHeight) {
                val w = mWidth
                mWidth = mHeight
                mHeight = w
            }
        }

        val 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
        )

        val inputSurfaceReference: AtomicReference<Surface> = AtomicReference<Surface>()

        videoEncoder = createVideoEncoder(
            videoCodecInfo, outputVideoFormat, inputSurfaceReference
        )

        inputSurface = InputSurface(inputSurfaceReference.get())
        inputSurface.makeCurrent()

        outputSurface = OutputSurface()

        videoDecoder = createVideoDecoder(inputFormat, outputSurface!!.surface!!);

        audioExtractor = createExtractor()

        val audioInputTrack = getAndSelectAudioTrackIndex(audioExtractor)

        val inputAudioFormat = audioExtractor!!.getTrackFormat(audioInputTrack)

        val 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 = MediaMuxer(mOutputFile!!, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)

        changeResolution(
            videoExtractor, audioExtractor,
            videoDecoder, videoEncoder,
            audioDecoder, audioEncoder,
            muxer, inputSurface, outputSurface
        )

    } finally {

        try {
            videoExtractor?.release()
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            audioExtractor?.release()
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            if (videoDecoder != null) {
                videoDecoder.stop()
                videoDecoder.release()
            }
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            outputSurface?.release()
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            if (videoEncoder != null) {
                videoEncoder.stop()
                videoEncoder.release()
            }
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            if (audioDecoder != null) {
                audioDecoder.stop()
                audioDecoder.release()
            }
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            if (audioEncoder != null) {
                audioEncoder.stop()
                audioEncoder.release()
            }
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            if (muxer != null) {
                muxer.stop()
                muxer.release()
            }
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

        try {
            inputSurface?.release()
        } catch (e: Exception) {
            if (exception == null) exception = e
        }

    }

    if (exception != null) throw exception

}

@Throws(IOException::class)
private fun createExtractor(): MediaExtractor? {

    val extractor : MediaExtractor = MediaExtractor()

    mInputFile?.let { extractor.setDataSource(it) }

    return extractor

}

@Throws(IOException::class)
private fun createVideoDecoder(inputFormat: MediaFormat, surface: Surface): MediaCodec? {

    val decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat)!!)
    decoder.configure(inputFormat, surface, null, 0)
    decoder.start()

    return decoder

}

@Throws(IOException::class)
private fun createVideoEncoder(
    codecInfo: MediaCodecInfo, format: MediaFormat,
    surfaceReference: AtomicReference<Surface>
): MediaCodec? {

    val encoder = MediaCodec.createByCodecName(codecInfo.name)
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

    surfaceReference.set(encoder.createInputSurface())

    encoder.start()

    return encoder

}

@Throws(IOException::class)
private fun createAudioDecoder(inputFormat: MediaFormat): MediaCodec? {

    val decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat)!!)
    decoder.configure(inputFormat, null, null, 0)
    decoder.start()

    return decoder

}

@Throws(IOException::class)
private fun createAudioEncoder(codecInfo: MediaCodecInfo, format: MediaFormat): MediaCodec? {

    val encoder = MediaCodec.createByCodecName(codecInfo.name)
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    encoder.start()

    return encoder

}

private fun getAndSelectVideoTrackIndex(extractor: MediaExtractor?): Int {

    for (index in 0 until extractor!!.trackCount) {

        if (isVideoFormat(extractor.getTrackFormat(index))) {

            extractor.selectTrack(index)

            return index

        }

    }

    return -1

}

private fun getAndSelectAudioTrackIndex(extractor: MediaExtractor?): Int {

    for (index in 0 until extractor!!.trackCount) {

        if (isAudioFormat(extractor.getTrackFormat(index))) {

            extractor.selectTrack(index)

            return index

        }

    }

    return -1

}

private fun changeResolution(
    videoExtractor: MediaExtractor?, audioExtractor: MediaExtractor?,
    videoDecoder: MediaCodec?, videoEncoder: MediaCodec?,
    audioDecoder: MediaCodec?, audioEncoder: MediaCodec?,
    muxer: MediaMuxer,
    inputSurface: InputSurface?, outputSurface: OutputSurface?
) {

    var videoDecoderInputBuffers : Array<ByteBuffer?>? = null
    var videoDecoderOutputBuffers : Array<ByteBuffer?>? = null
    var videoEncoderOutputBuffers : Array<ByteBuffer?>? = null
    var videoDecoderOutputBufferInfo : MediaCodec.BufferInfo? = null
    var videoEncoderOutputBufferInfo : MediaCodec.BufferInfo? = null
    videoDecoderInputBuffers = videoDecoder!!.inputBuffers
    videoDecoderOutputBuffers = videoDecoder.outputBuffers
    videoEncoderOutputBuffers = videoEncoder!!.outputBuffers
    videoDecoderOutputBufferInfo = MediaCodec.BufferInfo()
    videoEncoderOutputBufferInfo = MediaCodec.BufferInfo()
    var audioDecoderInputBuffers : Array<ByteBuffer?>? = null
    var audioDecoderOutputBuffers : Array<ByteBuffer>? = null
    var audioEncoderInputBuffers : Array<ByteBuffer>? = null
    var audioEncoderOutputBuffers : Array<ByteBuffer?>? = null
    var audioDecoderOutputBufferInfo : MediaCodec.BufferInfo? = null
    var audioEncoderOutputBufferInfo : MediaCodec.BufferInfo? = null
    audioDecoderInputBuffers = audioDecoder!!.inputBuffers
    audioDecoderOutputBuffers = audioDecoder.outputBuffers
    audioEncoderInputBuffers = audioEncoder!!.inputBuffers
    audioEncoderOutputBuffers = audioEncoder.outputBuffers
    audioDecoderOutputBufferInfo = MediaCodec.BufferInfo()
    audioEncoderOutputBufferInfo = MediaCodec.BufferInfo()
    var encoderOutputVideoFormat : MediaFormat? = null
    var encoderOutputAudioFormat : MediaFormat? = null
    var outputVideoTrack = -1
    var outputAudioTrack = -1
    var videoExtractorDone = false
    var videoDecoderDone = false
    var videoEncoderDone = false
    var audioExtractorDone = false
    var audioDecoderDone = false
    var audioEncoderDone = false
    var pendingAudioDecoderOutputBufferIndex = -1
    var muxing = false

    while (!videoEncoderDone || !audioEncoderDone) {

        while (!videoExtractorDone
            && (encoderOutputVideoFormat == null || muxing)
        ) {

            val decoderInputBufferIndex = videoDecoder.dequeueInputBuffer(TIMEOUT_USEC.toLong())

            if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)  {

                break

            }

            val decoderInputBuffer: ByteBuffer? =
                videoDecoderInputBuffers[decoderInputBufferIndex]

            val size = decoderInputBuffer?.let { videoExtractor!!.readSampleData(it, 0) }

            val presentationTime = videoExtractor?.sampleTime

            if (presentationTime != null) {

                if (size != null) {

                    if (size >= 0) {

                        if (videoExtractor != null) {

                            videoDecoder.queueInputBuffer(
                                decoderInputBufferIndex,
                                0,
                                size,
                                presentationTime,
                                videoExtractor.sampleFlags
                            )

                        }

                    }

                }

            }

            if (videoExtractor != null) {

                videoExtractorDone = (!videoExtractor.advance() && size == -1)

            }

            if (videoExtractorDone) {

                videoDecoder.queueInputBuffer(
                    decoderInputBufferIndex,
                    0,
                    0,
                    0,
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                )

            }

            break

        }

        while (!audioExtractorDone
            && (encoderOutputAudioFormat == null || muxing)
        ) {

            val decoderInputBufferIndex = audioDecoder.dequeueInputBuffer(TIMEOUT_USEC.toLong())

            if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {

                break

            }

            val decoderInputBuffer: ByteBuffer? =
                audioDecoderInputBuffers[decoderInputBufferIndex]

            val size = decoderInputBuffer?.let { audioExtractor!!.readSampleData(it, 0) }

            val presentationTime = audioExtractor?.sampleTime

            if (presentationTime != null) {

                if (size != null) {

                    if (size >= 0) {

                        audioDecoder.queueInputBuffer(
                            decoderInputBufferIndex,
                            0,
                            size,
                            presentationTime,
                            audioExtractor.sampleFlags
                        )

                    }

                }

            }

            if (audioExtractor != null) {    
                audioExtractorDone = (!audioExtractor.advance() && size == -1)
            }

            if (audioExtractorDone) {

                audioDecoder.queueInputBuffer(
                    decoderInputBufferIndex,
                    0,
                    0,
                    0,
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                )

            }

            break

        }

        while (!videoDecoderDone
            && (encoderOutputVideoFormat == null || muxing)
        ) {

            val decoderOutputBufferIndex = videoDecoder.dequeueOutputBuffer(
                videoDecoderOutputBufferInfo, TIMEOUT_USEC.toLong()
            )

            if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {

                break

            }

            if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

                videoDecoderOutputBuffers = videoDecoder.outputBuffers

                break

            }

            if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

                decoderOutputVideoFormat = videoDecoder.outputFormat

                break

            }

            val decoderOutputBuffer: ByteBuffer? =
                videoDecoderOutputBuffers!![decoderOutputBufferIndex]


            if (videoDecoderOutputBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG
                != 0
            ) {

                videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false)

                break

            }

            val render = videoDecoderOutputBufferInfo.size != 0

            videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, render)

            if (render) {

                if (outputSurface != null) {

                    outputSurface.awaitNewImage()
                    outputSurface.drawImage()

                }

                if (inputSurface != null) {

                    inputSurface.setPresentationTime(
                        videoDecoderOutputBufferInfo.presentationTimeUs * 1000
                    )
                    inputSurface.swapBuffers()

                }

            }

            if ((videoDecoderOutputBufferInfo.flags
                        and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
            ) {

                videoDecoderDone = true
                videoEncoder.signalEndOfInputStream()

            }

            break

        }

        while (!audioDecoderDone && pendingAudioDecoderOutputBufferIndex == -1 && (encoderOutputAudioFormat == null || muxing)) {

            val decoderOutputBufferIndex = audioDecoder.dequeueOutputBuffer(
                audioDecoderOutputBufferInfo, TIMEOUT_USEC.toLong()
            )

            if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {

                break

            }

            if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

                audioDecoderOutputBuffers = audioDecoder.outputBuffers
                break

            }

            if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

                decoderOutputAudioFormat = audioDecoder.outputFormat
                break
            }

            val decoderOutputBuffer: ByteBuffer =
                audioDecoderOutputBuffers!![decoderOutputBufferIndex]

            if (audioDecoderOutputBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG
                != 0
            ) {

                audioDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false)

                break
            }

            pendingAudioDecoderOutputBufferIndex = decoderOutputBufferIndex

            break

        }

        while (pendingAudioDecoderOutputBufferIndex != -1) {

            val encoderInputBufferIndex = audioEncoder.dequeueInputBuffer(TIMEOUT_USEC.toLong())

            val encoderInputBuffer: ByteBuffer =
                audioEncoderInputBuffers[encoderInputBufferIndex]

            val size = audioDecoderOutputBufferInfo.size

            val presentationTime = audioDecoderOutputBufferInfo.presentationTimeUs

            if (size >= 0) {

                val decoderOutputBuffer: ByteBuffer =
                    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
                        and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
            ) audioDecoderDone = true

            break

        }

        while (!videoEncoderDone
            && (encoderOutputVideoFormat == null || muxing)
        ) {

            val encoderOutputBufferIndex = videoEncoder.dequeueOutputBuffer(
                videoEncoderOutputBufferInfo, TIMEOUT_USEC.toLong()
            )

            if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) break

            if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                videoEncoderOutputBuffers = videoEncoder.outputBuffers
                break
            }

            if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                encoderOutputVideoFormat = videoEncoder.outputFormat
                break
            }

            val encoderOutputBuffer: ByteBuffer? =
                videoEncoderOutputBuffers!![encoderOutputBufferIndex]

            if (videoEncoderOutputBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG
                != 0
            ) {
                videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false)
                break
            }

            if (videoEncoderOutputBufferInfo.size != 0) {
                if (encoderOutputBuffer != null) {
                    muxer.writeSampleData(
                        outputVideoTrack, encoderOutputBuffer, videoEncoderOutputBufferInfo
                    )
                }
            }

            if (videoEncoderOutputBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM
                != 0
            ) {
                videoEncoderDone = true
            }

            videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false)

            break

        }

        while (!audioEncoderDone
            && (encoderOutputAudioFormat == null || muxing)
        ) {

            val encoderOutputBufferIndex = audioEncoder.dequeueOutputBuffer(
                audioEncoderOutputBufferInfo, TIMEOUT_USEC.toLong()
            )

            if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                break
            }

            if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                audioEncoderOutputBuffers = audioEncoder.outputBuffers
                break
            }

            if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                encoderOutputAudioFormat = audioEncoder.outputFormat
                break
            }

            val encoderOutputBuffer: ByteBuffer? =
                audioEncoderOutputBuffers!![encoderOutputBufferIndex]

            if (audioEncoderOutputBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG
                != 0
            ) {

                audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false)

                break

            }

            if (audioEncoderOutputBufferInfo.size != 0) encoderOutputBuffer?.let {

                muxer.writeSampleData(
                    outputAudioTrack, it, audioEncoderOutputBufferInfo
                )

            }

            if (audioEncoderOutputBufferInfo.flags and 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 fun isVideoFormat(format: MediaFormat): Boolean {

    return getMimeTypeFor(format)!!.startsWith("video/")
}

private fun isAudioFormat(format: MediaFormat): Boolean {

    return getMimeTypeFor(format)!!.startsWith("audio/")
}

private fun getMimeTypeFor(format: MediaFormat): String? {

    return format.getString(MediaFormat.KEY_MIME)
}

private fun selectCodec(mimeType: String): MediaCodecInfo? {

    val numCodecs = MediaCodecList.getCodecCount()

    for (i in 0 until numCodecs) {

        val codecInfo = MediaCodecList.getCodecInfoAt(i)

        if (!codecInfo.isEncoder) {
            continue
        }

        val types = codecInfo.supportedTypes

        for (j in types.indices) {
            if (types[j].equals(mimeType, ignoreCase = true)) {
                return codecInfo
            }
        }

    }

    return null

}

}
于 2021-10-16T13:09:57.060 回答
0

输入表面.kt

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Holds state associated with a Surface used for MediaCodec encoder input.
 *
 *
 * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
 * to create an EGL window surface.  Calls to eglSwapBuffers() cause a frame of data to be sent
 * to the video encoder.
 */
internal class InputSurface(surface: Surface?) {
    private var mEGLDisplay = EGL14.EGL_NO_DISPLAY
    private var mEGLContext = EGL14.EGL_NO_CONTEXT
    private var mEGLSurface = EGL14.EGL_NO_SURFACE

    /**
     * Returns the Surface that the MediaCodec receives buffers from.
     */
    var surface: Surface?
        private set

    /**
     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports recording.
     */
    private fun eglSetup() {
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("unable to get EGL14 display")
        }
        val version = IntArray(2)
        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
            mEGLDisplay = null
            throw RuntimeException("unable to initialize EGL14")
        }
        // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
        // to minimize artifacts from possible YUV conversion.
        val attribList = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL_RECORDABLE_ANDROID, 1,
            EGL14.EGL_NONE
        )
        val configs = arrayOfNulls<EGLConfig>(1)
        val numConfigs = IntArray(1)
        if (!EGL14.eglChooseConfig(
                mEGLDisplay, attribList, 0, configs, 0, configs.size,
                numConfigs, 0
            )
        ) {
            throw RuntimeException("unable to find RGB888+recordable ES2 EGL config")
        }
        // Configure context for OpenGL ES 2.0.
        val attrib_list = intArrayOf(
            EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL14.EGL_NONE
        )
        mEGLContext = EGL14.eglCreateContext(
            mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
            attrib_list, 0
        )
        checkEglError("eglCreateContext")
        if (mEGLContext == null) {
            throw RuntimeException("null context")
        }
        // Create a window surface, and attach it to the Surface we received.
        val surfaceAttribs = intArrayOf(
            EGL14.EGL_NONE
        )
        mEGLSurface = EGL14.eglCreateWindowSurface(
            mEGLDisplay, configs[0], surface,
            surfaceAttribs, 0
        )
        checkEglError("eglCreateWindowSurface")
        if (mEGLSurface == null) {
            throw RuntimeException("surface was null")
        }
    }

    /**
     * Discard all resources held by this class, notably the EGL context.  Also releases the
     * Surface that was passed to our constructor.
     */
    fun release() {
        if (mEGLDisplay !== EGL14.EGL_NO_DISPLAY) {
            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface)
            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
            EGL14.eglReleaseThread()
            EGL14.eglTerminate(mEGLDisplay)
        }
        surface!!.release()
        mEGLDisplay = EGL14.EGL_NO_DISPLAY
        mEGLContext = EGL14.EGL_NO_CONTEXT
        mEGLSurface = EGL14.EGL_NO_SURFACE
        surface = null
    }

    /**
     * Makes our EGL context and surface current.
     */
    fun makeCurrent() {
        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    fun makeUnCurrent() {
        if (!EGL14.eglMakeCurrent(
                mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                EGL14.EGL_NO_CONTEXT
            )
        ) {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    /**
     * Calls eglSwapBuffers.  Use this to "publish" the current frame.
     */
    fun swapBuffers(): Boolean {
        //println("swapBuffers")
        return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface)
    }

    /**
     * Queries the surface's width.
     */
    val width: Int
        get() {
            val value = IntArray(1)
            EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0)
            return value[0]
        }

    /**
     * Queries the surface's height.
     */
    val height: Int
        get() {
            val value = IntArray(1)
            EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0)
            return value[0]
        }

    /**
     * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
     */
    fun setPresentationTime(nsecs: Long) {
        EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs)
    }

    /**
     * Checks for EGL errors.
     */
    private fun checkEglError(msg: String) {
        var error: Int
        if (EGL14.eglGetError().also { error = it } != EGL14.EGL_SUCCESS) {
            throw RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error))
        }
    }

    companion object {
        private const val TAG = "InputSurface"
        private const val VERBOSE = false
        private const val EGL_RECORDABLE_ANDROID = 0x3142
    }

    /**
     * Creates an InputSurface from a Surface.
     */
    init {
        if (surface == null) {
            throw NullPointerException()
        }
        this.surface = surface
        eglSetup()
    }
}
于 2021-10-16T13:12:11.727 回答
0

输出表面.kt

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Holds state associated with a Surface used for MediaCodec decoder output.
 *
 *
 * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture,
 * and then create a Surface for that SurfaceTexture.  The Surface can be passed to
 * MediaCodec.configure() to receive decoder output.  When a frame arrives, we latch the
 * texture with updateTexImage, then render the texture with GL to a pbuffer.
 *
 *
 * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer.
 * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives
 * we just draw it on whatever surface is current.
 *
 *
 * By default, the Surface will be using a BufferQueue in asynchronous mode, so we
 * can potentially drop frames.
 */
internal class OutputSurface : OnFrameAvailableListener {
    private var mEGLDisplay = EGL14.EGL_NO_DISPLAY
    private var mEGLContext = EGL14.EGL_NO_CONTEXT
    private var mEGLSurface = EGL14.EGL_NO_SURFACE
    private var mSurfaceTexture: SurfaceTexture? = null

    /**
     * Returns the Surface that we draw onto.
     */
    var surface: Surface? = null
        private set
    private val mFrameSyncObject = Object() // guards mFrameAvailable
    private var mFrameAvailable = false
    private var mTextureRender: TextureRender? = null

    /**
     * Creates an OutputSurface backed by a pbuffer with the specifed dimensions.  The new
     * EGL context and surface will be made current.  Creates a Surface that can be passed
     * to MediaCodec.configure().
     */
    constructor(width: Int, height: Int) {
        println("OutputSurface constructor width: $width height: $height")
        require(!(width <= 0 || height <= 0))
        eglSetup(width, height)
        makeCurrent()
        setup()
    }

    /**
     * Creates an OutputSurface using the current EGL context (rather than establishing a
     * new one).  Creates a Surface that can be passed to MediaCodec.configure().
     */
    constructor() {
        println("OutputSurface constructor")
        setup()
    }

    /**
     * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
     * with the SurfaceTexture.
     */
    private fun setup() {
        println("OutputSurface setup")
        mTextureRender = TextureRender()
        mTextureRender!!.surfaceCreated()
        // Even if we don't access the SurfaceTexture after the constructor returns, we
        // still need to keep a reference to it.  The Surface doesn't retain a reference
        // at the Java level, so if we don't either then the object can get GCed, which
        // causes the native finalizer to run.
        if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender!!.textureId)
        mSurfaceTexture = SurfaceTexture(mTextureRender!!.textureId)
        // This doesn't work if OutputSurface is created on the thread that CTS started for
        // these test cases.
        //
        // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
        // create a Handler that uses it.  The "frame available" message is delivered
        // there, but since we're not a Looper-based thread we'll never see it.  For
        // this to do anything useful, OutputSurface must be created on a thread without
        // a Looper, so that SurfaceTexture uses the main application Looper instead.
        //
        // Java language note: passing "this" out of a constructor is generally unwise,
        // but we should be able to get away with it here.
        mSurfaceTexture!!.setOnFrameAvailableListener(this)
        surface = Surface(mSurfaceTexture)
    }

    /**
     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports pbuffer.
     */
    private fun eglSetup(width: Int, height: Int) {
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("unable to get EGL14 display")
        }
        val version = IntArray(2)
        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
            mEGLDisplay = null
            throw RuntimeException("unable to initialize EGL14")
        }
        // Configure EGL for pbuffer and OpenGL ES 2.0.  We want enough RGB bits
        // to be able to tell if the frame is reasonable.
        val attribList = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
            EGL14.EGL_NONE
        )
        val configs = arrayOfNulls<EGLConfig>(1)
        val numConfigs = IntArray(1)
        if (!EGL14.eglChooseConfig(
                mEGLDisplay, attribList, 0, configs, 0, configs.size,
                numConfigs, 0
            )
        ) {
            throw RuntimeException("unable to find RGB888+recordable ES2 EGL config")
        }
        // Configure context for OpenGL ES 2.0.
        val attrib_list = intArrayOf(
            EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL14.EGL_NONE
        )
        mEGLContext = EGL14.eglCreateContext(
            mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
            attrib_list, 0
        )
        checkEglError("eglCreateContext")
        if (mEGLContext == null) {
            throw RuntimeException("null context")
        }
        // Create a pbuffer surface.  By using this for output, we can use glReadPixels
        // to test values in the output.
        val surfaceAttribs = intArrayOf(
            EGL14.EGL_WIDTH, width,
            EGL14.EGL_HEIGHT, height,
            EGL14.EGL_NONE
        )
        mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0)
        checkEglError("eglCreatePbufferSurface")
        if (mEGLSurface == null) {
            throw RuntimeException("surface was null")
        }
    }

    /**
     * Discard all resources held by this class, notably the EGL context.
     */
    fun release() {
        if (mEGLDisplay !== EGL14.EGL_NO_DISPLAY) {
            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface)
            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
            EGL14.eglReleaseThread()
            EGL14.eglTerminate(mEGLDisplay)
        }
        surface!!.release()
        // this causes a bunch of warnings that appear harmless but might confuse someone:
        //  W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
        //mSurfaceTexture.release();
        mEGLDisplay = EGL14.EGL_NO_DISPLAY
        mEGLContext = EGL14.EGL_NO_CONTEXT
        mEGLSurface = EGL14.EGL_NO_SURFACE
        mTextureRender = null
        surface = null
        mSurfaceTexture = null
    }

    /**
     * Makes our EGL context and surface current.
     */
    private fun makeCurrent() {
        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
            throw RuntimeException("eglMakeCurrent failed")
        }
    }

    /**
     * Replaces the fragment shader.
     */
    fun changeFragmentShader(fragmentShader: String?) {
        if (fragmentShader != null) {
            mTextureRender?.changeFragmentShader(fragmentShader)
        }
    }

    /**
     * Latches the next buffer into the texture.  Must be called from the thread that created
     * the OutputSurface object, after the onFrameAvailable callback has signaled that new
     * data is available.
     */

    fun awaitNewImage() {

        //println("awaitNewImage")

        val TIMEOUT_MS = 500

        synchronized(mFrameSyncObject) {

            while (!mFrameAvailable) {

                try {

                    // Wait for onFrameAvailable() to signal us.  Use a timeout to avoid
                    // stalling the test if it doesn't arrive.

                    mFrameSyncObject.wait(TIMEOUT_MS.toLong())

                    if (!mFrameAvailable) {
                        // TODO: if "spurious wakeup", continue while loop
                        //throw RuntimeException("Surface frame wait timed out")
                    }

                } catch (ie: InterruptedException) {

                    // shouldn't happen
                    throw RuntimeException(ie)

                }

            }

            mFrameAvailable = false

        }

        // Latch the data.

        mTextureRender?.checkGlError("before updateTexImage")
        mSurfaceTexture!!.updateTexImage()

    }

    /**
     * Draws the data from SurfaceTexture onto the current EGL surface.
     */
    fun drawImage() {
        mSurfaceTexture?.let { mTextureRender?.drawFrame(it) }
    }

    override fun onFrameAvailable(st: SurfaceTexture) {
        //println("onFrameAvailable")
        if (VERBOSE) Log.d(TAG, "new frame available")
        synchronized(mFrameSyncObject) {
            if (mFrameAvailable) {
                throw RuntimeException("mFrameAvailable already set, frame could be dropped")
            }
            mFrameAvailable = true
            mFrameSyncObject.notifyAll()
        }
    }

    /**
     * Checks for EGL errors.
     */
    private fun checkEglError(msg: String) {
        var error: Int
        if (EGL14.eglGetError().also { error = it } != EGL14.EGL_SUCCESS) {
            throw RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error))
        }
    }

    companion object {
        private const val TAG = "OutputSurface"
        private const val VERBOSE = false
    }

}
于 2021-10-16T13:10:57.377 回答