Skip to content

Commit

Permalink
Get rid of flush() causes broken frames on some devices
Browse files Browse the repository at this point in the history
Fixes ypresto#8
  • Loading branch information
ypresto committed May 21, 2015
1 parent c398488 commit 14fcd52
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy
mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
setupMetadata();
setupTrackTranscoders(formatStrategy);
mMuxer.start();
runPipelines();
mMuxer.stop();
} finally {
Expand Down Expand Up @@ -156,25 +155,26 @@ private void setupTrackTranscoders(MediaFormatStrategy formatStrategy) {
if (videoOutputFormat == null && audioOutputFormat == null) {
throw new InvalidOutputFormatException("MediaFormatStrategy returned pass-through for both video and audio. No transcoding is necessary.");
}
QueuedMuxer queuedMuxer = new QueuedMuxer(mMuxer, new QueuedMuxer.Listener() {
@Override
public void onDetermineOutputFormat() {
MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat());
MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat());
}
});

if (videoOutputFormat == null) {
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, mMuxer);
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, queuedMuxer, QueuedMuxer.SampleType.VIDEO);
} else {
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, mMuxer);
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, queuedMuxer);
}
mVideoTrackTranscoder.setup();
if (audioOutputFormat == null) {
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, mMuxer);
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, queuedMuxer, QueuedMuxer.SampleType.AUDIO);
} else {
throw new UnsupportedOperationException("Transcoding audio tracks currently not supported.");
}
mAudioTrackTranscoder.setup();
mVideoTrackTranscoder.determineFormat();
mAudioTrackTranscoder.determineFormat();
MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat());
MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat());
mVideoTrackTranscoder.addTrackToMuxer();
mAudioTrackTranscoder.addTrackToMuxer();
mExtractor.selectTrack(trackResult.mVideoTrackIndex);
mExtractor.selectTrack(trackResult.mAudioTrackIndex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,32 @@
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;

import java.nio.ByteBuffer;

public class PassThroughTrackTranscoder implements TrackTranscoder {
private final MediaExtractor mExtractor;
private final int mTrackIndex;
private final MediaMuxer mMuxer;
private final QueuedMuxer mMuxer;
private final QueuedMuxer.SampleType mSampleType;
private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
private int mOutputTrackIndex = -1;
private int mBufferSize;
private ByteBuffer mBuffer;
private boolean mIsEOS;
private MediaFormat mActualOutputFormat;
private long mWrittenPresentationTimeUs;

public PassThroughTrackTranscoder(MediaExtractor extractor,
int trackIndex,
MediaMuxer muxer) {
public PassThroughTrackTranscoder(MediaExtractor extractor, int trackIndex,
QueuedMuxer muxer, QueuedMuxer.SampleType sampleType) {
mExtractor = extractor;
mTrackIndex = trackIndex;
mMuxer = muxer;
mSampleType = sampleType;

mActualOutputFormat = mExtractor.getTrackFormat(mTrackIndex);
mMuxer.setOutputFormat(mSampleType, mActualOutputFormat);
mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
mBuffer = ByteBuffer.allocateDirect(mBufferSize);
}

@Override
Expand All @@ -52,18 +56,6 @@ public MediaFormat getDeterminedFormat() {
return mActualOutputFormat;
}

@Override
public void addTrackToMuxer() {
mOutputTrackIndex = mMuxer.addTrack(mActualOutputFormat);
mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
mBuffer = ByteBuffer.allocateDirect(mBufferSize);
}

@Override
public void determineFormat() {
mActualOutputFormat = mExtractor.getTrackFormat(mTrackIndex);
}

@SuppressLint("Assert")
@Override
public boolean stepPipeline() {
Expand All @@ -72,7 +64,7 @@ public boolean stepPipeline() {
if (trackIndex < 0) {
mBuffer.clear();
mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
mMuxer.writeSampleData(mOutputTrackIndex, mBuffer, mBufferInfo);
mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
mIsEOS = true;
return true;
}
Expand All @@ -84,7 +76,7 @@ public boolean stepPipeline() {
boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0;
mBufferInfo.set(0, sampleSize, mExtractor.getSampleTime(), flags);
mMuxer.writeSampleData(mOutputTrackIndex, mBuffer, mBufferInfo);
mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;

mExtractor.advance();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package net.ypresto.androidtranscoder.engine;

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

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
* This class queues until all output track formats are determined.
*/
public class QueuedMuxer {
private static final String TAG = "QueuedMuxer";
private static final int BUFFER_SIZE = 64 * 1024; // I have no idea whether this value is appropriate or not...
private final MediaMuxer mMuxer;
private final Listener mListener;
private MediaFormat mVideoFormat;
private MediaFormat mAudioFormat;
private int mVideoTrackIndex;
private int mAudioTrackIndex;
private ByteBuffer mByteBuffer;
private List<SampleInfo> mSampleInfoList;
private boolean mStarted;

public QueuedMuxer(MediaMuxer muxer, Listener listener) {
mMuxer = muxer;
mListener = listener;
mSampleInfoList = new ArrayList<>();
}

public void setOutputFormat(SampleType sampleType, MediaFormat format) {
switch (sampleType) {
case VIDEO:
mVideoFormat = format;
break;
case AUDIO:
mAudioFormat = format;
break;
default:
throw new AssertionError();
}
onSetOutputFormat();
}

private void onSetOutputFormat() {
if (mVideoFormat == null || mAudioFormat == null) return;
mListener.onDetermineOutputFormat();

mVideoTrackIndex = mMuxer.addTrack(mVideoFormat);
Log.v(TAG, "Added track #" + mVideoTrackIndex + " with " + mVideoFormat.getString(MediaFormat.KEY_MIME) + " to muxer");
mAudioTrackIndex = mMuxer.addTrack(mAudioFormat);
Log.v(TAG, "Added track #" + mAudioTrackIndex + " with " + mAudioFormat.getString(MediaFormat.KEY_MIME) + " to muxer");
mMuxer.start();
mByteBuffer.flip();
Log.v(TAG, "Output format determined, writing " + mSampleInfoList.size() +
" samples / " + mByteBuffer.limit() + " bytes to muxer.");
mStarted = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int offset = 0;
for (SampleInfo sampleInfo : mSampleInfoList) {
sampleInfo.writeToBufferInfo(bufferInfo, offset);
mMuxer.writeSampleData(getTrackIndexForSampleType(sampleInfo.mSampleType), mByteBuffer, bufferInfo);
offset += sampleInfo.mSize;
}
mSampleInfoList = null;
mByteBuffer = null;
}

public void writeSampleData(SampleType sampleType, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo) {
if (mStarted) {
mMuxer.writeSampleData(getTrackIndexForSampleType(sampleType), byteBuf, bufferInfo);
return;
}
byteBuf.limit(bufferInfo.offset + bufferInfo.size);
byteBuf.position(bufferInfo.offset);
if (mByteBuffer == null) {
mByteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE).order(byteBuf.order());
}
mByteBuffer.put(byteBuf);
mSampleInfoList.add(new SampleInfo(sampleType, bufferInfo.size, bufferInfo));
}

private int getTrackIndexForSampleType(SampleType sampleType) {
switch (sampleType) {
case VIDEO:
return mVideoTrackIndex;
case AUDIO:
return mAudioTrackIndex;
default:
throw new AssertionError();
}
}

public enum SampleType {VIDEO, AUDIO}

private static class SampleInfo {
private final SampleType mSampleType;
private final int mSize;
private final long mPresentationTimeUs;
private final int mFlags;

private SampleInfo(SampleType sampleType, int size, MediaCodec.BufferInfo bufferInfo) {
mSampleType = sampleType;
mSize = size;
mPresentationTimeUs = bufferInfo.presentationTimeUs;
mFlags = bufferInfo.flags;
}

private void writeToBufferInfo(MediaCodec.BufferInfo bufferInfo, int offset) {
bufferInfo.set(offset, mSize, mPresentationTimeUs, mFlags);
}
}

public interface Listener {
void onDetermineOutputFormat();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,12 @@ public interface TrackTranscoder {

/**
* Get actual MediaFormat which is used to write to muxer.
* To determine you should call {@link #determineFormat()}.
* To determine you should call {@link #stepPipeline()} several times.
*
* @return Actual output format determined by coder, or {@code null} if not yet determined.
*/
MediaFormat getDeterminedFormat();

/**
* You should call this after {@link #determineFormat()} and before {@link #stepPipeline()}.
* When all transcoder added their tracks then you may call {@link android.media.MediaMuxer#start()}.
*/
void addTrackToMuxer();

/**
* Fill pipeline without writing to muxer until actual output format is determined.
* You should not select any tracks on MediaExtractor to determine correctly.
*/
void determineFormat();

/**
* Step pipeline if output is available in any step of it.
* It assumes muxer has been started, so you should call muxer.start() first.
Expand Down
Loading

0 comments on commit 14fcd52

Please sign in to comment.