aboutsummaryrefslogtreecommitdiffstats
path: root/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java')
-rw-r--r--libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java209
1 files changed, 209 insertions, 0 deletions
diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java
new file mode 100644
index 000000000..47e0a61d9
--- /dev/null
+++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java
@@ -0,0 +1,209 @@
+package net.ypresto.androidtranscoder.engine;
+
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+
+import net.ypresto.androidtranscoder.compat.MediaCodecBufferCompatWrapper;
+
+import java.io.IOException;
+
+public class AudioTrackTranscoder implements TrackTranscoder {
+
+ private static final QueuedMuxer.SampleType SAMPLE_TYPE = QueuedMuxer.SampleType.AUDIO;
+
+ 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 QueuedMuxer mMuxer;
+ private long mWrittenPresentationTimeUs;
+
+ private final int mTrackIndex;
+ private final MediaFormat mInputFormat;
+ private final MediaFormat mOutputFormat;
+
+ private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
+ private MediaCodec mDecoder;
+ private MediaCodec mEncoder;
+ private MediaFormat mActualOutputFormat;
+
+ private MediaCodecBufferCompatWrapper mDecoderBuffers;
+ private MediaCodecBufferCompatWrapper mEncoderBuffers;
+
+ private boolean mIsExtractorEOS;
+ private boolean mIsDecoderEOS;
+ private boolean mIsEncoderEOS;
+ private boolean mDecoderStarted;
+ private boolean mEncoderStarted;
+
+ private AudioChannel mAudioChannel;
+
+ public AudioTrackTranscoder(MediaExtractor extractor, int trackIndex,
+ MediaFormat outputFormat, QueuedMuxer muxer) {
+ mExtractor = extractor;
+ mTrackIndex = trackIndex;
+ mOutputFormat = outputFormat;
+ mMuxer = muxer;
+
+ mInputFormat = mExtractor.getTrackFormat(mTrackIndex);
+ }
+
+ @Override
+ public void setup() {
+ mExtractor.selectTrack(mTrackIndex);
+ try {
+ mEncoder = MediaCodec.createEncoderByType(mOutputFormat.getString(MediaFormat.KEY_MIME));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ mEncoder.configure(mOutputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ mEncoder.start();
+ mEncoderStarted = true;
+ mEncoderBuffers = new MediaCodecBufferCompatWrapper(mEncoder);
+
+ final MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex);
+ try {
+ mDecoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ mDecoder.configure(inputFormat, null, null, 0);
+ mDecoder.start();
+ mDecoderStarted = true;
+ mDecoderBuffers = new MediaCodecBufferCompatWrapper(mDecoder);
+
+ mAudioChannel = new AudioChannel(mDecoder, mEncoder, mOutputFormat);
+ }
+
+ @Override
+ public MediaFormat getDeterminedFormat() {
+ return mInputFormat;
+ }
+
+ @Override
+ public boolean stepPipeline() {
+ boolean busy = false;
+
+ int status;
+ while (drainEncoder(0) != 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 (mAudioChannel.feedEncoder(0)) busy = true;
+ while (drainExtractor(0) != DRAIN_STATE_NONE) busy = true;
+
+ return busy;
+ }
+
+ private int drainExtractor(long timeoutUs) {
+ if (mIsExtractorEOS) return DRAIN_STATE_NONE;
+ int trackIndex = mExtractor.getSampleTrackIndex();
+ if (trackIndex >= 0 && trackIndex != mTrackIndex) {
+ return DRAIN_STATE_NONE;
+ }
+
+ final 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;
+ }
+
+ final int sampleSize = mExtractor.readSampleData(mDecoderBuffers.getInputBuffer(result), 0);
+ final 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:
+ mAudioChannel.setActualDecodedFormat(mDecoder.getOutputFormat());
+ case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
+ return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
+ }
+
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mIsDecoderEOS = true;
+ mAudioChannel.drainDecoderBufferAndQueue(AudioChannel.BUFFER_INDEX_END_OF_STREAM, 0);
+ } else if (mBufferInfo.size > 0) {
+ mAudioChannel.drainDecoderBufferAndQueue(result, mBufferInfo.presentationTimeUs);
+ }
+
+ return DRAIN_STATE_CONSUMED;
+ }
+
+ private int drainEncoder(long timeoutUs) {
+ 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:
+ if (mActualOutputFormat != null) {
+ throw new RuntimeException("Audio output format changed twice.");
+ }
+ mActualOutputFormat = mEncoder.getOutputFormat();
+ mMuxer.setOutputFormat(SAMPLE_TYPE, mActualOutputFormat);
+ return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
+ case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
+ mEncoderBuffers = new MediaCodecBufferCompatWrapper(mEncoder);
+ return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
+ }
+
+ if (mActualOutputFormat == null) {
+ throw new RuntimeException("Could not determine actual output format.");
+ }
+
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mIsEncoderEOS = true;
+ mBufferInfo.set(0, 0, 0, mBufferInfo.flags);
+ }
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ // SPS or PPS, which should be passed by MediaFormat.
+ mEncoder.releaseOutputBuffer(result, false);
+ return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
+ }
+ mMuxer.writeSampleData(SAMPLE_TYPE, mEncoderBuffers.getOutputBuffer(result), mBufferInfo);
+ mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
+ mEncoder.releaseOutputBuffer(result, false);
+ return DRAIN_STATE_CONSUMED;
+ }
+
+ @Override
+ public long getWrittenPresentationTimeUs() {
+ return mWrittenPresentationTimeUs;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return mIsEncoderEOS;
+ }
+
+ @Override
+ public void release() {
+ if (mDecoder != null) {
+ if (mDecoderStarted) mDecoder.stop();
+ mDecoder.release();
+ mDecoder = null;
+ }
+ if (mEncoder != null) {
+ if (mEncoderStarted) mEncoder.stop();
+ mEncoder.release();
+ mEncoder = null;
+ }
+ }
+}