如何在Android中重新排列视频帧?

时间:2016-09-22 03:50:08

标签: android video-processing mediacodec

我正在开发视频编辑应用,但我遇到了一些问题。我想重新安排视频帧。我有一个想法。第一个视频解码, 并且帧解码数据存储在列表中,重新排列并生成新视频。

我使用Android Framework中的MediaCodec类来完成编解码工作。但我不熟悉它。根据我的想法,我编写了以下代码:

public class VideoProcesser {

    private static final String TAG = "VideoProcesser";

    private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory();

    private static final String OUTPUT_VIDEO_MIME_TYPE = "video/avc";
    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000;   // 2Mbps
    private static final int OUTPUT_VIDEO_FRAME_RATE = 15;      // 15fps
    private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10; // 10 seconds between I-frames

    private Context mContext;

    public VideoProcesser(Context mContext) {
        this.mContext = mContext;
    }

    public void startProcessing() {

        VideoChunks longChunks = generateVideoChunks(new IntegerPair(0, 180));
        VideoChunks shortChunks = generateVideoChunks(new IntegerPair(30, 60));

        VideoChunks dstChunks = new VideoChunks();
        dstChunks.addChunks(longChunks);
        dstChunks.addChunks(shortChunks);

        saveChunksToFile(dstChunks, new File(OUTPUT_FILENAME_DIR, "sample_processed.mp4"));
    }

    private void saveChunksToFile(VideoChunks inputData, File file) {
        MediaMuxer muxer = null;
        MediaCodec videoEncoder = null;

        try {
            MediaFormat outputFormat =
                    MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, 1280, 720);
            outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
            outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
            outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);

            MediaCodecInfo videoEncoderInfo = selectCodec(getMimeTypeFor(outputFormat));
            int colorFormat = selectColorFormat(videoEncoderInfo, OUTPUT_VIDEO_MIME_TYPE);
            outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);

            videoEncoder = MediaCodec.createByCodecName(videoEncoderInfo.getName());
            videoEncoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            videoEncoder.start();

            muxer = new MediaMuxer(file.getAbsolutePath(),
                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

            doSaveVideoChunks(inputData, videoEncoder, muxer);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            Log.d(TAG, "shutting down encoder, muxer");
            if (videoEncoder != null) {
                videoEncoder.stop();
                videoEncoder.release();
            }
            if (muxer != null) {
                muxer.stop();
                muxer.release();
            }
        }
    }

    private void doSaveVideoChunks(VideoChunks inputData, MediaCodec videoEncoder,
            MediaMuxer muxer) {
        final int TIMEOUT_USEC = 10000;

        ByteBuffer[] videoEncoderInputBuffers = videoEncoder.getInputBuffers();
        ByteBuffer[] videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
        MediaCodec.BufferInfo videoEncoderOutputBufferInfo = new MediaCodec.BufferInfo();

        int inputChunk = 0;
        int videoEncodedFrameCount = 0;

        boolean inputDone = false;
        boolean videoEncodeDone = false;

        boolean muxing = false;
        MediaFormat encoderOutputVideoFormat = null;
        int outputVideoTrack = -1;

        while (!videoEncodeDone) {
            Log.d(TAG, String.format("save loop: inputChunk:%s encoded:%s", inputChunk,
                    videoEncodedFrameCount));

            if (!inputDone && (encoderOutputVideoFormat == null || muxing)) {
                int encoderInputStatus = videoEncoder.dequeueInputBuffer(TIMEOUT_USEC);
                if (encoderInputStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    Log.d(TAG, "no video encoder input buffer");
                    continue;
                }

                long time = computePresentationTime(inputChunk);

                if (inputChunk == inputData.getNumChunks()) {
                    // End of stream -- send empty frame with EOS flag set.
                    videoEncoder.queueInputBuffer(encoderInputStatus, 0, 0, 0L,
                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    inputDone = true;
                    Log.d(TAG, "input data EOS");
                } else {
                    ByteBuffer encoderInputBuffer = videoEncoderInputBuffers[encoderInputStatus];
                    encoderInputBuffer.clear();
                    inputData.getChunkData(inputChunk, encoderInputBuffer);

                    videoEncoder.queueInputBuffer(encoderInputStatus, 0,
                            encoderInputBuffer.position(), time,
                            inputData.getChunkFlags(inputChunk));

                    inputChunk++;
                }
            }

            if (!videoEncodeDone && (encoderOutputVideoFormat == null || muxing)) {
                int encoderOutputStatus =
                        videoEncoder.dequeueOutputBuffer(videoEncoderOutputBufferInfo,
                                TIMEOUT_USEC);

                if (encoderOutputStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    Log.d(TAG, "no video encoder output buffer");
                } else if (encoderOutputStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    Log.d(TAG, "video encoder: output buffers changed");
                    videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
                } else if (encoderOutputStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    encoderOutputVideoFormat = videoEncoder.getOutputFormat();
                    Log.d(TAG, "video encoder: output media format changed"
                            + encoderOutputVideoFormat);
                    outputVideoTrack = muxer.addTrack(encoderOutputVideoFormat);
                    muxing = true;
                    muxer.start();
                } else if (encoderOutputStatus < 0) {
                    //ignore it
                } else {
                    ByteBuffer encoderOutputBuffer = videoEncoderOutputBuffers[encoderOutputStatus];

                    if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                            != 0) {
                        Log.d(TAG, "video encoder: codec config buffer");
                        videoEncoder.releaseOutputBuffer(encoderOutputStatus, false);
                        continue;
                        // Simply ignore codec config buffers.
                    }
                    if (videoEncoderOutputBufferInfo.size != 0) {
                        muxer.writeSampleData(outputVideoTrack, encoderOutputBuffer,
                                videoEncoderOutputBufferInfo);
                    }
                    if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                            != 0) {
                        Log.d(TAG, "video encoder: EOS");
                        videoEncodeDone = true;
                    }
                    videoEncoder.releaseOutputBuffer(encoderOutputStatus, false);
                    videoEncodedFrameCount++;
                }
            }
        }
    }

    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;
    }

    private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
        for (int i = 0; i < capabilities.colorFormats.length; i++) {
            int colorFormat = capabilities.colorFormats[i];
            if (isRecognizedFormat(colorFormat)) {
                return colorFormat;
            }
        }
        Log.e(TAG,
                "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
        return 0;   // not reached
    }

    private static boolean isRecognizedFormat(int colorFormat) {
        switch (colorFormat) {
            // these are the formats we know how to handle for this test
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
                return true;
            default:
                return false;
        }
    }

    private static long computePresentationTime(int frameIndex) {
        return 132 + frameIndex * 1000000 / OUTPUT_VIDEO_FRAME_RATE;
    }

    private VideoChunks generateVideoChunks(IntegerPair segment) {
        VideoChunks outputData = new VideoChunks();

        MediaExtractor videoExtractor = null;
        MediaCodec videoDecoder = null;
        try {

            videoExtractor = createVideoExtractor(R.raw.sample);
            int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
            MediaFormat videoInputFormat = videoExtractor.getTrackFormat(videoInputTrack);

            videoDecoder = createVideoDecoder(videoInputFormat);

            doGenerateVideoChunks(segment, videoExtractor, videoDecoder, outputData);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            Log.d(TAG, "shutting down extractor, decoder");
            if (videoExtractor != null) {
                videoExtractor.release();
            }
            if (videoDecoder != null) {
                videoDecoder.stop();
                videoDecoder.release();
            }
        }

        return outputData;
    }

    private void doGenerateVideoChunks(IntegerPair segment, MediaExtractor videoExtractor,
            MediaCodec videoDecoder, VideoChunks outputData) {

        final int TIMEOUT_USEC = 10000;
        ByteBuffer[] videoDecoderInputBuffers = videoDecoder.getInputBuffers();
        ByteBuffer[] videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
        MediaCodec.BufferInfo videoDecoderOutputBufferInfo = new MediaCodec.BufferInfo();

        int videoExtractedFrameCount = 0;
        int videoDecodedFrameCount = 0;

        boolean videoExtracted = false;
        boolean videoDecoded = false;

        while (true) {

            Log.d(TAG, String.format("loop: extracted:%s decoded:%s", videoExtractedFrameCount,
                    videoDecodedFrameCount));

            while (!videoExtracted) {
                int decoderInputStatus = videoDecoder.dequeueInputBuffer(TIMEOUT_USEC);
                if (decoderInputStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    Log.d(TAG, "no video decoder input buffer");
                    break;
                }

                ByteBuffer decoderInputBuffer = videoDecoderInputBuffers[decoderInputStatus];

                int size = videoExtractor.readSampleData(decoderInputBuffer, 0);
                long presentationTime = videoExtractor.getSampleTime();
                int flags = videoExtractor.getSampleFlags();

                if (size >= 0) {
                    videoDecoder.queueInputBuffer(decoderInputStatus, 0, size, presentationTime,
                            flags);
                }

                videoExtracted = !videoExtractor.advance();

                if (videoExtracted) {
                    Log.d(TAG, "video extractor: EOS");
                    videoDecoder.queueInputBuffer(decoderInputStatus, 0, 0, 0L,
                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                }
                videoExtractedFrameCount++;
                break;
            }

            while (!videoDecoded) {
                int decoderOutputStatus =
                        videoDecoder.dequeueOutputBuffer(videoDecoderOutputBufferInfo,
                                TIMEOUT_USEC);

                if (decoderOutputStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    Log.d(TAG, "no video decoder output buffer");
                    break;
                }
                if (decoderOutputStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    Log.d(TAG, "video decoder: output buffers changed");
                    videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
                    break;
                }
                if (decoderOutputStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat newFormat = videoDecoder.getOutputFormat();
                    Log.d(TAG, "video decoder: output format changed: " + newFormat);
                    break;
                }

                ByteBuffer decoderOutputBuffer = videoDecoderOutputBuffers[decoderOutputStatus];

                if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                        != 0) {
                    Log.d(TAG, "video decoder: codec config buffer");
                    videoDecoder.releaseOutputBuffer(decoderOutputStatus, false);
                    break;
                }

                boolean render = videoDecoderOutputBufferInfo.size != 0;
                if (render) {
                    if (segment.contains(videoDecodedFrameCount)) {
                        outputData.addChunk(decoderOutputBuffer, videoDecoderOutputBufferInfo.flags,
                                videoDecoderOutputBufferInfo.presentationTimeUs);
                    }
                }
                videoDecoder.releaseOutputBuffer(decoderOutputStatus, render);
                if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                        != 0) {
                    Log.d(TAG, "video decoder: EOS");
                    videoDecoded = true;
                }

                videoDecodedFrameCount++;
                break;
            }

            if (videoDecoded) {
                break;
            }
        }
    }

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

    private MediaExtractor createVideoExtractor(@RawRes int resId) throws IOException {
        MediaExtractor extractor = new MediaExtractor();

        AssetFileDescriptor srcFd = mContext.getResources().openRawResourceFd(resId);
        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
                srcFd.getLength());

        return extractor;
    }

    private int getAndSelectVideoTrackIndex(MediaExtractor extractor) {
        for (int index = 0; index < extractor.getTrackCount(); ++index) {
            Log.d(TAG, "format for track " + index + " is " + getMimeTypeFor(
                    extractor.getTrackFormat(index)));
            if (isVideoFormat(extractor.getTrackFormat(index))) {
                extractor.selectTrack(index);
                return index;
            }
        }
        return -1;
    }

    private static String getMimeTypeFor(MediaFormat format) {
        return format.getString(MediaFormat.KEY_MIME);
    }

    private static boolean isVideoFormat(MediaFormat format) {
        return getMimeTypeFor(format).startsWith("video/");
    }
}

但是,我收到的视频丢失了原始视频数据。您可以帮助我找到上述代码的错误。或者有任何好主意。

这是我的full code

edit1:我发现了问题。输出视频的大小不正确是问题的关键。所以我保持与原始视频相同的大小,一切都很好。最后,别忘了视频的方向,否则您的输出视频可能有点奇怪。

0 个答案:

没有答案
相关问题