public class MediaCodecWrapper {
private final static String TAG = "MediaCodecWrapper";
static MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
public interface OutputFormatChangedListener {
void outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat);
}
private OutputFormatChangedListener mOutputFormatChangedListener = null;
public interface OutputSampleListener {
void outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer);
}
private OutputSampleListener mOutputSampleListener = null;
private MediaCodec mDecoder;
private ByteBuffer[] mInputBuffers;
private ByteBuffer[] mOutputBuffers;
private Queue<Integer> mAvailableInputBuffers;
private Queue<Integer> mAvailableOutputBuffers;
private MediaCodec.BufferInfo[] mOutputBufferInfo;
private MediaCodecWrapper(MediaCodec codec) {
mDecoder = codec;
mDecoder.start();
mInputBuffers = mDecoder.getInputBuffers();
mOutputBuffers = mDecoder.getOutputBuffers();
Log.e(TAG, "MediaCodecWrapper: " + mInputBuffers.length + " <-> " + mOutputBuffers.length);
mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
mAvailableInputBuffers = new ArrayDeque<Integer>(mOutputBuffers.length);
mAvailableOutputBuffers = new ArrayDeque<Integer>(mInputBuffers.length);
}
public void stopAndRelease() {
mDecoder.stop();
mDecoder.release();
mDecoder = null;
}
public static MediaCodecWrapper findVideoMediaCodecWrapper(final MediaFormat trackFormat,
Surface surface) throws IOException {
MediaCodecWrapper result = null;
final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
if (mimeType.contains("video/")) {
MediaCodec videoCodec = MediaCodec.createDecoderByType(mimeType);
videoCodec.configure(trackFormat, surface, null, 0);
result = new MediaCodecWrapper(videoCodec);
}
return result;
}
public boolean writeSample(final MediaExtractor extractor,
final boolean isSecure,
final long presentationTimeUs,
int flags) {
boolean result = false;
if (!mAvailableInputBuffers.isEmpty()) {
int index = mAvailableInputBuffers.remove();
ByteBuffer buffer = mInputBuffers[index];
int size = extractor.readSampleData(buffer, 0);
if (size <= 0) {
flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
}
if (!isSecure) {
Log.e(TAG, "writeSample: " + index + " -> " + size);
mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
} else {
extractor.getSampleCryptoInfo(cryptoInfo);
mDecoder.queueSecureInputBuffer(index, 0, cryptoInfo, presentationTimeUs, flags);
}
result = true;
}
return result;
}
public boolean peekSample(MediaCodec.BufferInfo out_bufferInfo) {
update();
boolean result = false;
if (!mAvailableOutputBuffers.isEmpty()) {
int index = mAvailableOutputBuffers.peek();
MediaCodec.BufferInfo info = mOutputBufferInfo[index];
out_bufferInfo.set(
info.offset,
info.size,
info.presentationTimeUs,
info.flags);
result = true;
}
return result;
}
* Processes, releases and optionally renders the output buffer available at the head of the queue.
*/
public void popSample(boolean render) {
update();
if (!mAvailableOutputBuffers.isEmpty()) {
int index = mAvailableOutputBuffers.remove();
Log.e(TAG, "popSample: " + index );
if (render && mOutputSampleListener != null) {
ByteBuffer buffer = mOutputBuffers[index];
MediaCodec.BufferInfo info = mOutputBufferInfo[index];
mOutputSampleListener.outputSample(this, info, buffer);
}
mDecoder.releaseOutputBuffer(index, render);
}
}
private void update() {
int index;
while ((index = mDecoder.dequeueInputBuffer(0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.e(TAG, "update#dequeueInputBuffer: " + index);
mAvailableInputBuffers.add(index);
}
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while ((index = mDecoder.dequeueOutputBuffer(info, 0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.e(TAG, "update#dequeueOutputBuffer: " + index);
switch (index) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
mOutputBuffers = mDecoder.getOutputBuffers();
mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
mAvailableOutputBuffers.clear();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
outputFormatChanged();
break;
default:
if (index >= 0) {
mOutputBufferInfo[index] = info;
mAvailableOutputBuffers.add(index);
}
break;
}
}
}
private void outputFormatChanged() {
if (mOutputFormatChangedListener != null) {
mOutputFormatChangedListener.outputFormatChanged(MediaCodecWrapper.this,
mDecoder.getOutputFormat());
}
}
private class WriteException extends Throwable {
private WriteException(final String detailMessage) {
super(detailMessage);
}
}
* Write a media sample to the decoder.
* <p>
* A "sample" here refers to a single atomic access unit in the media stream. The definition
* of "access unit" is dependent on the type of encoding used, but it typically refers to
* a single frame of video or a few seconds of audio. {@link MediaExtractor}
* extracts data from a stream one sample at a time.
*
* @param input A ByteBuffer containing the input data for one sample. The buffer must be set
* up for reading, with its position set to the beginning of the sample data and its limit
* set to the end of the sample data.
* @param presentationTimeUs The time, relative to the beginning of the media stream,
* at which this buffer should be rendered.
* @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int,
* int, int, long, int)}
*/
public boolean writeSample(final ByteBuffer input,
final MediaCodec.CryptoInfo crypto,
final long presentationTimeUs,
final int flags) throws MediaCodec.CryptoException, WriteException {
boolean result = false;
int size = input.remaining();
if (size > 0 && !mAvailableInputBuffers.isEmpty()) {
int index = mAvailableInputBuffers.remove();
ByteBuffer buffer = mInputBuffers[index];
if (size > buffer.capacity()) {
throw new WriteException(String.format(
"Insufficient capacity in MediaCodec buffer: "
+ "tried to write %d, buffer capacity is %d.",
input.remaining(),
buffer.capacity()));
}
buffer.clear();
buffer.put(input);
if (crypto == null) {
mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
} else {
mDecoder.queueSecureInputBuffer(index, 0, crypto, presentationTimeUs, flags);
}
result = true;
}
return result;
}
}