Skip to content

Commit b44b9a4

Browse files
author
Aaron Wong
committed
Enable audio transcode (w/o resampling)
1 parent 5d098ca commit b44b9a4

File tree

7 files changed

+594
-12
lines changed

7 files changed

+594
-12
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package net.ypresto.androidtranscoder.compat;
2+
3+
import android.media.MediaCodec;
4+
import android.os.Build;
5+
6+
import java.nio.ByteBuffer;
7+
8+
/**
9+
* A Wrapper to MediaCodec that facilitates the use of API-dependent get{Input/Output}Buffer methods,
10+
* in order to prevent: http://stackoverflow.com/q/30646885
11+
*/
12+
public class MediaCodecBufferCompatWrapper {
13+
14+
final MediaCodec mMediaCodec;
15+
final ByteBuffer[] mInputBuffers;
16+
final ByteBuffer[] mOutputBuffers;
17+
18+
public MediaCodecBufferCompatWrapper(MediaCodec mediaCodec) {
19+
mMediaCodec = mediaCodec;
20+
21+
if (Build.VERSION.SDK_INT < 21) {
22+
mInputBuffers = mediaCodec.getInputBuffers();
23+
mOutputBuffers = mediaCodec.getOutputBuffers();
24+
} else {
25+
mInputBuffers = mOutputBuffers = null;
26+
}
27+
}
28+
29+
public ByteBuffer getInputBuffer(final int index) {
30+
if (Build.VERSION.SDK_INT >= 21) {
31+
return mMediaCodec.getInputBuffer(index);
32+
}
33+
return mInputBuffers[index];
34+
}
35+
36+
public ByteBuffer getOutputBuffer(final int index) {
37+
if (Build.VERSION.SDK_INT >= 21) {
38+
return mMediaCodec.getOutputBuffer(index);
39+
}
40+
return mOutputBuffers[index];
41+
}
42+
}
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package net.ypresto.androidtranscoder.engine;
2+
3+
import android.media.MediaCodec;
4+
import android.media.MediaFormat;
5+
6+
import net.ypresto.androidtranscoder.compat.MediaCodecBufferCompatWrapper;
7+
import net.ypresto.androidtranscoder.utils.AudioConversionUtils;
8+
9+
import java.nio.ByteBuffer;
10+
import java.nio.ByteOrder;
11+
import java.nio.ShortBuffer;
12+
import java.util.ArrayDeque;
13+
import java.util.Queue;
14+
15+
/**
16+
* Channel of raw audio from decoder to encoder.
17+
* Performs the necessary conversion between different input & output audio formats.
18+
*
19+
* We currently support upmixing from mono to stereo & downmixing from stereo to mono.
20+
* Sample rate conversion is not supported yet.
21+
*/
22+
class AudioChannel {
23+
24+
private static class AudioBuffer {
25+
int bufferIndex;
26+
long presentationTimeUs;
27+
ShortBuffer data;
28+
}
29+
30+
public static final int BUFFER_INDEX_END_OF_STREAM = -1;
31+
32+
private static final int BYTES_PER_SHORT = 2;
33+
private static final long MICROSECS_PER_SEC = 1000000;
34+
35+
private final Queue<AudioBuffer> mEmptyBuffers = new ArrayDeque<>();
36+
private final Queue<AudioBuffer> mFilledBuffers = new ArrayDeque<>();
37+
38+
private final MediaCodec mDecoder;
39+
private final MediaCodec mEncoder;
40+
private final MediaFormat mEncodeFormat;
41+
42+
private int mInputSampleRate;
43+
private int mInputChannelCount;
44+
private int mOutputChannelCount;
45+
46+
private AudioConversionUtils.Remixer mRemixer;
47+
48+
private final MediaCodecBufferCompatWrapper mDecoderBuffers;
49+
private final MediaCodecBufferCompatWrapper mEncoderBuffers;
50+
51+
private final AudioBuffer mOverflowBuffer = new AudioBuffer();
52+
53+
private MediaFormat mActualDecodedFormat;
54+
55+
56+
public AudioChannel(final MediaCodec decoder,
57+
final MediaCodec encoder, final MediaFormat encodeFormat) {
58+
mDecoder = decoder;
59+
mEncoder = encoder;
60+
mEncodeFormat = encodeFormat;
61+
62+
mDecoderBuffers = new MediaCodecBufferCompatWrapper(mDecoder);
63+
mEncoderBuffers = new MediaCodecBufferCompatWrapper(mEncoder);
64+
}
65+
66+
public void setActualDecodedFormat(final MediaFormat decodedFormat) {
67+
mActualDecodedFormat = decodedFormat;
68+
69+
mInputSampleRate = mActualDecodedFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
70+
if (mInputSampleRate != mEncodeFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)) {
71+
throw new UnsupportedOperationException("Audio sample rate conversion not supported yet.");
72+
}
73+
74+
mInputChannelCount = mActualDecodedFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
75+
mOutputChannelCount = mEncodeFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
76+
77+
if (mInputChannelCount != 1 && mInputChannelCount != 2) {
78+
throw new UnsupportedOperationException("Input channel count (" + mInputChannelCount + ") not supported.");
79+
}
80+
81+
if (mOutputChannelCount != 1 && mOutputChannelCount != 2) {
82+
throw new UnsupportedOperationException("Output channel count (" + mOutputChannelCount + ") not supported.");
83+
}
84+
85+
if (mInputChannelCount > mOutputChannelCount) {
86+
mRemixer = AudioConversionUtils.REMIXER_DOWNMIX;
87+
} else if (mInputChannelCount < mOutputChannelCount) {
88+
mRemixer = AudioConversionUtils.REMIXER_UPMIX;
89+
} else {
90+
mRemixer = AudioConversionUtils.REMIXER_PASSTHROUGH;
91+
}
92+
93+
mOverflowBuffer.presentationTimeUs = 0;
94+
}
95+
96+
public void drainDecoderBufferAndQueue(final int bufferIndex, final long presentationTimeUs) {
97+
if (mActualDecodedFormat == null) {
98+
throw new RuntimeException("Buffer received before format!");
99+
}
100+
101+
final ByteBuffer data =
102+
bufferIndex == BUFFER_INDEX_END_OF_STREAM ?
103+
null : mDecoderBuffers.getOutputBuffer(bufferIndex);
104+
105+
AudioBuffer buffer = mEmptyBuffers.poll();
106+
if (buffer == null) {
107+
buffer = new AudioBuffer();
108+
}
109+
110+
buffer.bufferIndex = bufferIndex;
111+
buffer.presentationTimeUs = presentationTimeUs;
112+
buffer.data = data == null ? null : data.asShortBuffer();
113+
114+
if (mOverflowBuffer.data == null) {
115+
mOverflowBuffer.data = ByteBuffer
116+
.allocateDirect(data.capacity())
117+
.order(ByteOrder.nativeOrder())
118+
.asShortBuffer();
119+
mOverflowBuffer.data.clear().flip();
120+
}
121+
122+
mFilledBuffers.add(buffer);
123+
}
124+
125+
public boolean feedEncoder(long timeoutUs) {
126+
final boolean hasOverflow = mOverflowBuffer.data != null && mOverflowBuffer.data.hasRemaining();
127+
if (mFilledBuffers.isEmpty() && !hasOverflow) {
128+
// No audio data - Bail out
129+
return false;
130+
}
131+
132+
final int encoderInBuffIndex = mEncoder.dequeueInputBuffer(timeoutUs);
133+
if (encoderInBuffIndex < 0) {
134+
// Encoder is full - Bail out
135+
return false;
136+
}
137+
138+
// Drain overflow first
139+
final ShortBuffer outBuffer = mEncoderBuffers.getInputBuffer(encoderInBuffIndex).asShortBuffer();
140+
if (hasOverflow) {
141+
final long presentationTimeUs = drainOverflow(outBuffer);
142+
mEncoder.queueInputBuffer(encoderInBuffIndex,
143+
0, outBuffer.position() * BYTES_PER_SHORT,
144+
presentationTimeUs, 0);
145+
return true;
146+
}
147+
148+
final AudioBuffer inBuffer = mFilledBuffers.poll();
149+
if (inBuffer.bufferIndex == BUFFER_INDEX_END_OF_STREAM) {
150+
mEncoder.queueInputBuffer(encoderInBuffIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
151+
return false;
152+
}
153+
154+
final long presentationTimeUs = remixAndMaybeFillOverflow(inBuffer, outBuffer);
155+
mEncoder.queueInputBuffer(encoderInBuffIndex,
156+
0, outBuffer.position() * BYTES_PER_SHORT,
157+
presentationTimeUs, 0);
158+
if (inBuffer != null) {
159+
mDecoder.releaseOutputBuffer(inBuffer.bufferIndex, false);
160+
mEmptyBuffers.add(inBuffer);
161+
}
162+
163+
return true;
164+
}
165+
166+
private static long sampleCountToDurationUs(final int sampleCount,
167+
final int sampleRate,
168+
final int channelCount) {
169+
return (sampleCount / (sampleRate * MICROSECS_PER_SEC)) / channelCount;
170+
}
171+
172+
private long drainOverflow(final ShortBuffer outBuff) {
173+
final ShortBuffer overflowBuff = mOverflowBuffer.data;
174+
final int overflowLimit = overflowBuff.limit();
175+
final int overflowSize = overflowBuff.remaining();
176+
177+
final long beginPresentationTimeUs = mOverflowBuffer.presentationTimeUs +
178+
sampleCountToDurationUs(overflowBuff.position(), mInputSampleRate, mOutputChannelCount);
179+
180+
outBuff.clear();
181+
// Limit overflowBuff to outBuff's capacity
182+
overflowBuff.limit(outBuff.capacity());
183+
// Load overflowBuff onto outBuff
184+
outBuff.put(overflowBuff);
185+
186+
if (overflowSize >= outBuff.capacity()) {
187+
// Overflow fully consumed - Reset
188+
overflowBuff.clear().limit(0);
189+
} else {
190+
// Only partially consumed - Keep position & restore previous limit
191+
overflowBuff.limit(overflowLimit);
192+
}
193+
194+
return beginPresentationTimeUs;
195+
}
196+
197+
private long remixAndMaybeFillOverflow(final AudioBuffer input,
198+
final ShortBuffer outBuff) {
199+
final ShortBuffer inBuff = input.data;
200+
final ShortBuffer overflowBuff = mOverflowBuffer.data;
201+
202+
outBuff.clear();
203+
204+
// Reset position to 0, and set limit to capacity (Since MediaCodec doesn't do that for us)
205+
inBuff.clear();
206+
207+
if (inBuff.remaining() > outBuff.remaining()) {
208+
// Overflow
209+
// Limit inBuff to outBuff's capacity
210+
inBuff.limit(outBuff.capacity());
211+
mRemixer.remix(inBuff, outBuff);
212+
213+
// Reset limit to its own capacity & Keep position
214+
inBuff.limit(inBuff.capacity());
215+
216+
// Remix the rest onto overflowBuffer
217+
// NOTE: We should only reach this point when overflow buffer is empty
218+
final long consumedDurationUs =
219+
sampleCountToDurationUs(inBuff.position(), mInputSampleRate, mInputChannelCount);
220+
mRemixer.remix(inBuff, overflowBuff);
221+
222+
// Seal off overflowBuff & mark limit
223+
overflowBuff.flip();
224+
mOverflowBuffer.presentationTimeUs = input.presentationTimeUs + consumedDurationUs;
225+
} else {
226+
// No overflow
227+
mRemixer.remix(inBuff, outBuff);
228+
}
229+
230+
return input.presentationTimeUs;
231+
}
232+
}

0 commit comments

Comments
 (0)