blob: 66fee33a1cfd3d1cec6dbaf2c8322b8246256ae5 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.media.benchmark.library;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaFormat;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
public class Decoder {
private static final String TAG = "Decoder";
private static final boolean DEBUG = false;
private static final int kQueueDequeueTimeoutUs = 1000;
private final Object mLock = new Object();
private MediaCodec mCodec;
private ArrayList<BufferInfo> mInputBufferInfo;
private Stats mStats;
private boolean mSawInputEOS;
private boolean mSawOutputEOS;
private boolean mSignalledError;
private int mNumOutputFrame;
private int mIndex;
private ArrayList<ByteBuffer> mInputBuffer;
private FileOutputStream mOutputStream;
public Decoder() { mStats = new Stats(); }
/**
* Setup of decoder
*
* @param outputStream Will dump the output in this stream if not null.
*/
public void setupDecoder(FileOutputStream outputStream) {
mSignalledError = false;
mOutputStream = outputStream;
}
private MediaCodec createCodec(String codecName, MediaFormat format) throws IOException {
String mime = format.getString(MediaFormat.KEY_MIME);
try {
MediaCodec codec;
if (codecName.isEmpty()) {
Log.i(TAG, "File mime type: " + mime);
if (mime != null) {
codec = MediaCodec.createDecoderByType(mime);
Log.i(TAG, "Decoder created for mime type " + mime);
return codec;
} else {
Log.e(TAG, "Mime type is null, please specify a mime type to create decoder");
return null;
}
} else {
codec = MediaCodec.createByCodecName(codecName);
Log.i(TAG, "Decoder created with codec name: " + codecName + " mime: " + mime);
return codec;
}
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
Log.e(TAG, "Failed to create decoder for " + codecName + " mime:" + mime);
return null;
}
}
/**
* Decodes the given input buffer,
* provided valid list of buffer info and format are passed as inputs.
*
* @param inputBuffer Decode the provided list of ByteBuffers
* @param inputBufferInfo List of buffer info corresponding to provided input buffers
* @param asyncMode Will run on async implementation if true
* @param format For creating the decoder if codec name is empty and configuring it
* @param codecName Will create the decoder with codecName
* @return 0 if decode was successful , -1 for fail, -2 for decoder not created
* @throws IOException if the codec cannot be created.
*/
public int decode(@NonNull ArrayList<ByteBuffer> inputBuffer,
@NonNull ArrayList<BufferInfo> inputBufferInfo, final boolean asyncMode,
@NonNull MediaFormat format, String codecName) throws IOException {
mInputBuffer = new ArrayList<>(inputBuffer.size());
mInputBuffer.addAll(inputBuffer);
mInputBufferInfo = new ArrayList<>(inputBufferInfo.size());
mInputBufferInfo.addAll(inputBufferInfo);
mSawInputEOS = false;
mSawOutputEOS = false;
mNumOutputFrame = 0;
mIndex = 0;
long sTime = mStats.getCurTime();
mCodec = createCodec(codecName, format);
if (mCodec == null) {
return -2;
}
if (asyncMode) {
mCodec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(
@NonNull MediaCodec mediaCodec, int inputBufferId) {
try {
mStats.addInputTime();
onInputAvailable(inputBufferId, mediaCodec);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.toString());
}
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec,
int outputBufferId, @NonNull MediaCodec.BufferInfo bufferInfo) {
mStats.addOutputTime();
onOutputAvailable(mediaCodec, outputBufferId, bufferInfo);
if (mSawOutputEOS) {
synchronized (mLock) { mLock.notify(); }
}
}
@Override
public void onOutputFormatChanged(
@NonNull MediaCodec mediaCodec, @NonNull MediaFormat format) {
Log.i(TAG, "Output format changed. Format: " + format.toString());
}
@Override
public void onError(
@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
mSignalledError = true;
Log.e(TAG, "Codec Error: " + e.toString());
e.printStackTrace();
synchronized (mLock) { mLock.notify(); }
}
});
}
int isEncoder = 0;
if (DEBUG) {
Log.d(TAG, "Media Format : " + format.toString());
}
mCodec.configure(format, null, null, isEncoder);
mCodec.start();
Log.i(TAG, "Codec started ");
long eTime = mStats.getCurTime();
mStats.setInitTime(mStats.getTimeDiff(sTime, eTime));
mStats.setStartTime();
if (asyncMode) {
try {
synchronized (mLock) { mLock.wait(); }
if (mSignalledError) {
return -1;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
while (!mSawOutputEOS && !mSignalledError) {
/* Queue input data */
if (!mSawInputEOS) {
int inputBufferId = mCodec.dequeueInputBuffer(kQueueDequeueTimeoutUs);
if (inputBufferId < 0 && inputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.e(TAG,
"MediaCodec.dequeueInputBuffer "
+ " returned invalid index : " + inputBufferId);
return -1;
}
mStats.addInputTime();
onInputAvailable(inputBufferId, mCodec);
}
/* Dequeue output data */
BufferInfo outputBufferInfo = new BufferInfo();
int outputBufferId =
mCodec.dequeueOutputBuffer(outputBufferInfo, kQueueDequeueTimeoutUs);
if (outputBufferId < 0) {
if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat outFormat = mCodec.getOutputFormat();
Log.i(TAG, "Output format changed. Format: " + outFormat.toString());
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
Log.i(TAG, "Ignoring deprecated flag: INFO_OUTPUT_BUFFERS_CHANGED");
} else if (outputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.e(TAG,
"MediaCodec.dequeueOutputBuffer"
+ " returned invalid index " + outputBufferId);
return -1;
}
} else {
mStats.addOutputTime();
if (DEBUG) {
Log.d(TAG, "Dequeue O/P buffer with BufferID " + outputBufferId);
}
onOutputAvailable(mCodec, outputBufferId, outputBufferInfo);
}
}
}
mInputBuffer.clear();
mInputBufferInfo.clear();
return 0;
}
/**
* Stops the codec and releases codec resources.
*/
public void deInitCodec() {
long sTime = mStats.getCurTime();
if (mCodec != null) {
mCodec.stop();
mCodec.release();
mCodec = null;
}
long eTime = mStats.getCurTime();
mStats.setDeInitTime(mStats.getTimeDiff(sTime, eTime));
}
/**
* Prints out the statistics in the information log
*
* @param inputReference The operation being performed, in this case decode
* @param componentName Name of the component/codec
* @param mode The operating mode: Sync/Async
* @param durationUs Duration of the clip in microseconds
* @param statsFile The output file where the stats data is written
*/
public void dumpStatistics(String inputReference, String componentName, String mode,
long durationUs, String statsFile) throws IOException {
String operation = "decode";
mStats.dumpStatistics(
inputReference, operation, componentName, mode, durationUs, statsFile);
}
/**
* Resets the stats
*/
public void resetDecoder() { mStats.reset(); }
/**
* Returns the format of the output buffers
*/
public MediaFormat getFormat() {
return mCodec.getOutputFormat();
}
private void onInputAvailable(int inputBufferId, MediaCodec mediaCodec) {
if ((inputBufferId >= 0) && !mSawInputEOS) {
ByteBuffer inputCodecBuffer = mediaCodec.getInputBuffer(inputBufferId);
BufferInfo bufInfo = mInputBufferInfo.get(mIndex);
inputCodecBuffer.put(mInputBuffer.get(mIndex).array());
mIndex++;
mSawInputEOS = (bufInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
if (mSawInputEOS) {
Log.i(TAG, "Saw input EOS");
}
mStats.addFrameSize(bufInfo.size);
mediaCodec.queueInputBuffer(inputBufferId, bufInfo.offset, bufInfo.size,
bufInfo.presentationTimeUs, bufInfo.flags);
if (DEBUG) {
Log.d(TAG,
"Codec Input: "
+ "flag = " + bufInfo.flags + " timestamp = "
+ bufInfo.presentationTimeUs + " size = " + bufInfo.size);
}
}
}
private void onOutputAvailable(
MediaCodec mediaCodec, int outputBufferId, BufferInfo outputBufferInfo) {
if (mSawOutputEOS || outputBufferId < 0) {
return;
}
mNumOutputFrame++;
if (DEBUG) {
Log.d(TAG,
"In OutputBufferAvailable ,"
+ " output frame number = " + mNumOutputFrame);
}
if (mOutputStream != null) {
try {
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId);
byte[] bytesOutput = new byte[outputBuffer.remaining()];
outputBuffer.get(bytesOutput);
mOutputStream.write(bytesOutput);
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Error Dumping File: Exception " + e.toString());
}
}
mediaCodec.releaseOutputBuffer(outputBufferId, false);
mSawOutputEOS = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
if (mSawOutputEOS) {
Log.i(TAG, "Saw output EOS");
}
}
}