Android Open Source - android-transcoder Video Track Transcoder






From Project

Back to project page android-transcoder.

License

The source code is released under:

Apache License

If you think the Android project android-transcoder listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2014 Yuya Tanaka/* w  w w.  j a  va  2  s  .co m*/
 *
 * 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.
 */
package net.ypresto.androidtranscoder.engine;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import java.nio.ByteBuffer;

// Refer: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
public class VideoTrackTranscoder implements TrackTranscoder {
    private static final String TAG = "VideoTrackTranscoder";
    private static final int DRAIN_STATE_NONE = 0;
    private static final int DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY = 1;
    private static final int DRAIN_STATE_CONSUMED = 2;

    private final MediaExtractor mExtractor;
    private final int mTrackIndex;
    private final MediaFormat mOutputFormat;
    private final MediaMuxer mMuxer;
    private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
    private boolean mWritingToMuxerStarted;
    private MediaCodec mDecoder;
    private MediaCodec mEncoder;
    private ByteBuffer[] mDecoderInputBuffers;
    private ByteBuffer[] mEncoderOutputBuffers;
    private MediaFormat mActualOutputFormat;
    private int mMuxerTrackIndex = -1;
    private OutputSurface mDecoderOutputSurfaceWrapper;
    private InputSurface mEncoderInputSurfaceWrapper;
    private boolean mIsExtractorEOS;
    private boolean mIsDecoderEOS;
    private boolean mIsEncoderEOS;
    private boolean mDecoderStarted;
    private boolean mEncoderStarted;
    private long mWrittenPresentationTimeUs;

    public VideoTrackTranscoder(MediaExtractor extractor,
                                int trackIndex,
                                MediaFormat outputFormat,
                                MediaMuxer muxer) {
        mExtractor = extractor;
        mTrackIndex = trackIndex;
        mOutputFormat = outputFormat;
        mMuxer = muxer;
    }

    @Override
    public void setup() {
        mExtractor.selectTrack(mTrackIndex);
        mEncoder = MediaCodec.createEncoderByType(mOutputFormat.getString(MediaFormat.KEY_MIME));
        mEncoder.configure(mOutputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mEncoderInputSurfaceWrapper = new InputSurface(mEncoder.createInputSurface());
        mEncoderInputSurfaceWrapper.makeCurrent();
        mEncoder.start();
        mEncoderStarted = true;
        mEncoderOutputBuffers = mEncoder.getOutputBuffers();

        MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex);
        if (inputFormat.containsKey("rotation-degrees")) {
            // Decoded video is rotated automatically in Android 5.0 lollipop.
            // Turn off here because we don't want to encode rotated one.
            // refer: https://android.googlesource.com/platform/frameworks/av/+blame/lollipop-release/media/libstagefright/Utils.cpp
            inputFormat.setInteger("rotation-degrees", 0);
        }
        mDecoderOutputSurfaceWrapper = new OutputSurface();
        mDecoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME));
        mDecoder.configure(inputFormat, mDecoderOutputSurfaceWrapper.getSurface(), null, 0);
        mDecoder.start();
        mDecoderStarted = true;
        mDecoderInputBuffers = mDecoder.getInputBuffers();
    }

    @Override
    public MediaFormat getDeterminedFormat() {
        return mActualOutputFormat;
    }

    @Override
    public void addTrackToMuxer() {
        if (mActualOutputFormat == null) {
            throw new IllegalStateException("Format is not determined yet.");
        }
        mMuxerTrackIndex = mMuxer.addTrack(mActualOutputFormat);
        Log.v(TAG, "Added track #" + mMuxerTrackIndex + " with " + mActualOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer");
    }

    @Override
    public void determineFormat() {
        try {
            mExtractor.selectTrack(mTrackIndex);
            while (mActualOutputFormat == null && !mIsEncoderEOS) {
                int trackIndex = mExtractor.getSampleTrackIndex();
                if (trackIndex >= 0 && trackIndex != mTrackIndex) {
                    throw new IllegalStateException("You should not select any tracks on MediaExtractor when calling determineFormat()."
                            + " Expected track " + mTrackIndex + " but received sample of track " + trackIndex + ".");
                }

                // fill pipeline
                drainExtractor(0);
                while (drainDecoder(0) == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY) ;
                while (mActualOutputFormat == null && drainEncoder(0, false) == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY) ;
            }
            if (mActualOutputFormat == null) {
                throw new IllegalStateException("Actual output format could not be determined for track: " + mTrackIndex);
            }
        } finally {
            resetCodecs();
            mExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); // reset position
            mExtractor.unselectTrack(mTrackIndex);
        }
    }

    @Override
    public boolean stepPipeline() {
        boolean busy = false;

        int status;
        while (drainEncoder(0, true) != DRAIN_STATE_NONE) { busy = true; }
        do {
            status = drainDecoder(0);
            if (status != DRAIN_STATE_NONE) busy = true;
            // NOTE: not repeating to keep from deadlock when encoder is full.
        } while (status == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY);
        while (drainExtractor(0) != DRAIN_STATE_NONE) { busy = true; }

        return busy;
    }

    @Override
    public long getWrittenPresentationTimeUs() {
        return mWrittenPresentationTimeUs;
    }

    @Override
    public boolean isFinished() {
        return mIsEncoderEOS;
    }

    // TODO: CloseGuard
    @Override
    public void release() {
        if (mDecoderOutputSurfaceWrapper != null) {
            mDecoderOutputSurfaceWrapper.release();
            mDecoderOutputSurfaceWrapper = null;
        }
        if (mEncoderInputSurfaceWrapper != null) {
            mEncoderInputSurfaceWrapper.release();
            mEncoderInputSurfaceWrapper = null;
        }
        if (mDecoder != null) {
            if (mDecoderStarted) mDecoder.stop();
            mDecoder.release();
            mDecoder = null;
        }
        if (mEncoder != null) {
            if (mEncoderStarted) mEncoder.stop();
            mEncoder.release();
            mEncoder = null;
        }
    }

    private void resetCodecs() {
        mDecoder.flush();
        mEncoder.flush();
        mIsExtractorEOS =  mIsDecoderEOS =  mIsEncoderEOS = false;
    }

    private int drainExtractor(long timeoutUs) {
        if (mIsExtractorEOS) return DRAIN_STATE_NONE;
        int trackIndex = mExtractor.getSampleTrackIndex();
        if (trackIndex >= 0 && trackIndex != mTrackIndex) {
            return DRAIN_STATE_NONE;
        }
        int result = mDecoder.dequeueInputBuffer(timeoutUs);
        if (result < 0) return DRAIN_STATE_NONE;
        if (trackIndex < 0) {
            mIsExtractorEOS = true;
            mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            return DRAIN_STATE_NONE;
        }
        int sampleSize = mExtractor.readSampleData(mDecoderInputBuffers[result], 0);
        boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
        mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0);
        mExtractor.advance();
        return DRAIN_STATE_CONSUMED;
    }

    private int drainDecoder(long timeoutUs) {
        if (mIsDecoderEOS) return DRAIN_STATE_NONE;
        int result = mDecoder.dequeueOutputBuffer(mBufferInfo, timeoutUs);
        switch (result) {
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                return DRAIN_STATE_NONE;
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
        }
        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            mEncoder.signalEndOfInputStream();
            mIsDecoderEOS = true;
            mBufferInfo.size = 0;
        }
        boolean doRender = (mBufferInfo.size > 0);
        // NOTE: doRender will block if buffer (of encoder) is full.
        // Refer: http://bigflake.com/mediacodec/CameraToMpegTest.java.txt
        mDecoder.releaseOutputBuffer(result, doRender);
        if (doRender) {
            mDecoderOutputSurfaceWrapper.awaitNewImage();
            mDecoderOutputSurfaceWrapper.drawImage();
            mEncoderInputSurfaceWrapper.setPresentationTime(mBufferInfo.presentationTimeUs * 1000);
            mEncoderInputSurfaceWrapper.swapBuffers();
        }
        return DRAIN_STATE_CONSUMED;
    }

    private int drainEncoder(long timeoutUs, boolean writeToMuxer) {
        if (mIsEncoderEOS) return DRAIN_STATE_NONE;
        int result = mEncoder.dequeueOutputBuffer(mBufferInfo, timeoutUs);
        switch (result) {
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                return DRAIN_STATE_NONE;
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                assert mActualOutputFormat == null;
                mActualOutputFormat = mEncoder.getOutputFormat();
                return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                mEncoderOutputBuffers = mEncoder.getOutputBuffers();
                return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
        }
        assert mActualOutputFormat != null;

        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            mIsEncoderEOS = true;
            mBufferInfo.set(0, 0, 0, mBufferInfo.flags);
        }
        if (writeToMuxer) {
            mMuxer.writeSampleData(mMuxerTrackIndex, mEncoderOutputBuffers[result], mBufferInfo);
            mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
        }
        mEncoder.releaseOutputBuffer(result, false);
        return DRAIN_STATE_CONSUMED;
    }
}




Java Source Code List

net.ypresto.androidtranscoder.ApplicationTest.java
net.ypresto.androidtranscoder.ApplicationTest.java
net.ypresto.androidtranscoder.MediaTranscoder.java
net.ypresto.androidtranscoder.compat.MediaCodecListCompat.java
net.ypresto.androidtranscoder.engine.InputSurface.java
net.ypresto.androidtranscoder.engine.MediaTranscoderEngine.java
net.ypresto.androidtranscoder.engine.OutputSurface.java
net.ypresto.androidtranscoder.engine.PassThroughTrackTranscoder.java
net.ypresto.androidtranscoder.engine.TextureRender.java
net.ypresto.androidtranscoder.engine.TrackTranscoder.java
net.ypresto.androidtranscoder.engine.VideoTrackTranscoder.java
net.ypresto.androidtranscoder.example.TranscoderActivity.java
net.ypresto.androidtranscoder.format.Android16By9FormatStrategy.java
net.ypresto.androidtranscoder.format.Android720pFormatStrategy.java
net.ypresto.androidtranscoder.format.ExportPreset960x540Strategy.java
net.ypresto.androidtranscoder.format.MediaFormatExtraConstants.java
net.ypresto.androidtranscoder.format.MediaFormatPresetsTest.java
net.ypresto.androidtranscoder.format.MediaFormatPresets.java
net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets.java
net.ypresto.androidtranscoder.format.MediaFormatStrategy.java
net.ypresto.androidtranscoder.format.OutputFormatUnavailableException.java
net.ypresto.androidtranscoder.utils.MediaExtractorUtils.java