Skip to content

Commit 14fcd52

Browse files
committed
Get rid of flush() causes broken frames on some devices
Fixes ypresto#8
1 parent c398488 commit 14fcd52

File tree

5 files changed

+161
-100
lines changed

5 files changed

+161
-100
lines changed

lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy
9393
mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
9494
setupMetadata();
9595
setupTrackTranscoders(formatStrategy);
96-
mMuxer.start();
9796
runPipelines();
9897
mMuxer.stop();
9998
} finally {
@@ -156,25 +155,26 @@ private void setupTrackTranscoders(MediaFormatStrategy formatStrategy) {
156155
if (videoOutputFormat == null && audioOutputFormat == null) {
157156
throw new InvalidOutputFormatException("MediaFormatStrategy returned pass-through for both video and audio. No transcoding is necessary.");
158157
}
158+
QueuedMuxer queuedMuxer = new QueuedMuxer(mMuxer, new QueuedMuxer.Listener() {
159+
@Override
160+
public void onDetermineOutputFormat() {
161+
MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat());
162+
MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat());
163+
}
164+
});
159165

160166
if (videoOutputFormat == null) {
161-
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, mMuxer);
167+
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, queuedMuxer, QueuedMuxer.SampleType.VIDEO);
162168
} else {
163-
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, mMuxer);
169+
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, queuedMuxer);
164170
}
165171
mVideoTrackTranscoder.setup();
166172
if (audioOutputFormat == null) {
167-
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, mMuxer);
173+
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, queuedMuxer, QueuedMuxer.SampleType.AUDIO);
168174
} else {
169175
throw new UnsupportedOperationException("Transcoding audio tracks currently not supported.");
170176
}
171177
mAudioTrackTranscoder.setup();
172-
mVideoTrackTranscoder.determineFormat();
173-
mAudioTrackTranscoder.determineFormat();
174-
MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat());
175-
MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat());
176-
mVideoTrackTranscoder.addTrackToMuxer();
177-
mAudioTrackTranscoder.addTrackToMuxer();
178178
mExtractor.selectTrack(trackResult.mVideoTrackIndex);
179179
mExtractor.selectTrack(trackResult.mAudioTrackIndex);
180180
}

lib/src/main/java/net/ypresto/androidtranscoder/engine/PassThroughTrackTranscoder.java

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,32 @@
1919
import android.media.MediaCodec;
2020
import android.media.MediaExtractor;
2121
import android.media.MediaFormat;
22-
import android.media.MediaMuxer;
2322

2423
import java.nio.ByteBuffer;
2524

2625
public class PassThroughTrackTranscoder implements TrackTranscoder {
2726
private final MediaExtractor mExtractor;
2827
private final int mTrackIndex;
29-
private final MediaMuxer mMuxer;
28+
private final QueuedMuxer mMuxer;
29+
private final QueuedMuxer.SampleType mSampleType;
3030
private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
31-
private int mOutputTrackIndex = -1;
3231
private int mBufferSize;
3332
private ByteBuffer mBuffer;
3433
private boolean mIsEOS;
3534
private MediaFormat mActualOutputFormat;
3635
private long mWrittenPresentationTimeUs;
3736

38-
public PassThroughTrackTranscoder(MediaExtractor extractor,
39-
int trackIndex,
40-
MediaMuxer muxer) {
37+
public PassThroughTrackTranscoder(MediaExtractor extractor, int trackIndex,
38+
QueuedMuxer muxer, QueuedMuxer.SampleType sampleType) {
4139
mExtractor = extractor;
4240
mTrackIndex = trackIndex;
4341
mMuxer = muxer;
42+
mSampleType = sampleType;
43+
44+
mActualOutputFormat = mExtractor.getTrackFormat(mTrackIndex);
45+
mMuxer.setOutputFormat(mSampleType, mActualOutputFormat);
46+
mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
47+
mBuffer = ByteBuffer.allocateDirect(mBufferSize);
4448
}
4549

4650
@Override
@@ -52,18 +56,6 @@ public MediaFormat getDeterminedFormat() {
5256
return mActualOutputFormat;
5357
}
5458

55-
@Override
56-
public void addTrackToMuxer() {
57-
mOutputTrackIndex = mMuxer.addTrack(mActualOutputFormat);
58-
mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
59-
mBuffer = ByteBuffer.allocateDirect(mBufferSize);
60-
}
61-
62-
@Override
63-
public void determineFormat() {
64-
mActualOutputFormat = mExtractor.getTrackFormat(mTrackIndex);
65-
}
66-
6759
@SuppressLint("Assert")
6860
@Override
6961
public boolean stepPipeline() {
@@ -72,7 +64,7 @@ public boolean stepPipeline() {
7264
if (trackIndex < 0) {
7365
mBuffer.clear();
7466
mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
75-
mMuxer.writeSampleData(mOutputTrackIndex, mBuffer, mBufferInfo);
67+
mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
7668
mIsEOS = true;
7769
return true;
7870
}
@@ -84,7 +76,7 @@ public boolean stepPipeline() {
8476
boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
8577
int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0;
8678
mBufferInfo.set(0, sampleSize, mExtractor.getSampleTime(), flags);
87-
mMuxer.writeSampleData(mOutputTrackIndex, mBuffer, mBufferInfo);
79+
mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
8880
mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
8981

9082
mExtractor.advance();
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package net.ypresto.androidtranscoder.engine;
2+
3+
import android.media.MediaCodec;
4+
import android.media.MediaFormat;
5+
import android.media.MediaMuxer;
6+
import android.util.Log;
7+
8+
import java.nio.ByteBuffer;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
/**
13+
* This class queues until all output track formats are determined.
14+
*/
15+
public class QueuedMuxer {
16+
private static final String TAG = "QueuedMuxer";
17+
private static final int BUFFER_SIZE = 64 * 1024; // I have no idea whether this value is appropriate or not...
18+
private final MediaMuxer mMuxer;
19+
private final Listener mListener;
20+
private MediaFormat mVideoFormat;
21+
private MediaFormat mAudioFormat;
22+
private int mVideoTrackIndex;
23+
private int mAudioTrackIndex;
24+
private ByteBuffer mByteBuffer;
25+
private List<SampleInfo> mSampleInfoList;
26+
private boolean mStarted;
27+
28+
public QueuedMuxer(MediaMuxer muxer, Listener listener) {
29+
mMuxer = muxer;
30+
mListener = listener;
31+
mSampleInfoList = new ArrayList<>();
32+
}
33+
34+
public void setOutputFormat(SampleType sampleType, MediaFormat format) {
35+
switch (sampleType) {
36+
case VIDEO:
37+
mVideoFormat = format;
38+
break;
39+
case AUDIO:
40+
mAudioFormat = format;
41+
break;
42+
default:
43+
throw new AssertionError();
44+
}
45+
onSetOutputFormat();
46+
}
47+
48+
private void onSetOutputFormat() {
49+
if (mVideoFormat == null || mAudioFormat == null) return;
50+
mListener.onDetermineOutputFormat();
51+
52+
mVideoTrackIndex = mMuxer.addTrack(mVideoFormat);
53+
Log.v(TAG, "Added track #" + mVideoTrackIndex + " with " + mVideoFormat.getString(MediaFormat.KEY_MIME) + " to muxer");
54+
mAudioTrackIndex = mMuxer.addTrack(mAudioFormat);
55+
Log.v(TAG, "Added track #" + mAudioTrackIndex + " with " + mAudioFormat.getString(MediaFormat.KEY_MIME) + " to muxer");
56+
mMuxer.start();
57+
mByteBuffer.flip();
58+
Log.v(TAG, "Output format determined, writing " + mSampleInfoList.size() +
59+
" samples / " + mByteBuffer.limit() + " bytes to muxer.");
60+
mStarted = true;
61+
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
62+
int offset = 0;
63+
for (SampleInfo sampleInfo : mSampleInfoList) {
64+
sampleInfo.writeToBufferInfo(bufferInfo, offset);
65+
mMuxer.writeSampleData(getTrackIndexForSampleType(sampleInfo.mSampleType), mByteBuffer, bufferInfo);
66+
offset += sampleInfo.mSize;
67+
}
68+
mSampleInfoList = null;
69+
mByteBuffer = null;
70+
}
71+
72+
public void writeSampleData(SampleType sampleType, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo) {
73+
if (mStarted) {
74+
mMuxer.writeSampleData(getTrackIndexForSampleType(sampleType), byteBuf, bufferInfo);
75+
return;
76+
}
77+
byteBuf.limit(bufferInfo.offset + bufferInfo.size);
78+
byteBuf.position(bufferInfo.offset);
79+
if (mByteBuffer == null) {
80+
mByteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE).order(byteBuf.order());
81+
}
82+
mByteBuffer.put(byteBuf);
83+
mSampleInfoList.add(new SampleInfo(sampleType, bufferInfo.size, bufferInfo));
84+
}
85+
86+
private int getTrackIndexForSampleType(SampleType sampleType) {
87+
switch (sampleType) {
88+
case VIDEO:
89+
return mVideoTrackIndex;
90+
case AUDIO:
91+
return mAudioTrackIndex;
92+
default:
93+
throw new AssertionError();
94+
}
95+
}
96+
97+
public enum SampleType {VIDEO, AUDIO}
98+
99+
private static class SampleInfo {
100+
private final SampleType mSampleType;
101+
private final int mSize;
102+
private final long mPresentationTimeUs;
103+
private final int mFlags;
104+
105+
private SampleInfo(SampleType sampleType, int size, MediaCodec.BufferInfo bufferInfo) {
106+
mSampleType = sampleType;
107+
mSize = size;
108+
mPresentationTimeUs = bufferInfo.presentationTimeUs;
109+
mFlags = bufferInfo.flags;
110+
}
111+
112+
private void writeToBufferInfo(MediaCodec.BufferInfo bufferInfo, int offset) {
113+
bufferInfo.set(offset, mSize, mPresentationTimeUs, mFlags);
114+
}
115+
}
116+
117+
public interface Listener {
118+
void onDetermineOutputFormat();
119+
}
120+
}

lib/src/main/java/net/ypresto/androidtranscoder/engine/TrackTranscoder.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,12 @@ public interface TrackTranscoder {
2323

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

32-
/**
33-
* You should call this after {@link #determineFormat()} and before {@link #stepPipeline()}.
34-
* When all transcoder added their tracks then you may call {@link android.media.MediaMuxer#start()}.
35-
*/
36-
void addTrackToMuxer();
37-
38-
/**
39-
* Fill pipeline without writing to muxer until actual output format is determined.
40-
* You should not select any tracks on MediaExtractor to determine correctly.
41-
*/
42-
void determineFormat();
43-
4432
/**
4533
* Step pipeline if output is available in any step of it.
4634
* It assumes muxer has been started, so you should call muxer.start() first.

0 commit comments

Comments
 (0)