Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8a30338

Browse files
authored
Merge pull request ypresto#17 from airtimemedia/audio-transcode
Enable audio transcode (w/o resampling)
2 parents db86ee9 + b44b9a4 commit 8a30338

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)

AltStyle によって変換されたページ (->オリジナル) /