blob: 48e14229e6ee7a814fc783833a61e4ad1a6a984b [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.tests;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaFormat;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.media.benchmark.R;
import com.android.media.benchmark.library.CodecUtils;
import com.android.media.benchmark.library.Decoder;
import com.android.media.benchmark.library.Encoder;
import com.android.media.benchmark.library.Extractor;
import com.android.media.benchmark.library.Native;
import com.android.media.benchmark.library.Stats;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class EncoderTest {
private static final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
private static final String mOutputFilePath = mContext.getString(R.string.output_file_path);
private static final String mStatsFile =
mContext.getExternalFilesDir(null) + "/Encoder." + System.currentTimeMillis() + ".csv";
private static final String TAG = "EncoderTest";
private static final long PER_TEST_TIMEOUT_MS = 120000;
private static final boolean DEBUG = false;
private static final boolean WRITE_OUTPUT = false;
private static final int ENCODE_DEFAULT_FRAME_RATE = 25;
private static final int ENCODE_DEFAULT_BIT_RATE = 8000000 /* 8 Mbps */;
private static final int ENCODE_MIN_BIT_RATE = 600000 /* 600 Kbps */;
private static final int ENCODE_DEFAULT_AUDIO_BIT_RATE = 128000 /* 128 Kbps */;
private String mInputFile;
@Parameterized.Parameters
public static Collection<Object[]> inputFiles() {
return Arrays.asList(new Object[][]{
// Audio Test
{"bbb_44100hz_2ch_128kbps_aac_30sec.mp4"},
{"bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp"},
{"bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp"},
{"bbb_44100hz_2ch_600kbps_flac_30sec.mp4"},
{"bbb_48000hz_2ch_100kbps_opus_30sec.webm"},
// Video Test
{"crowd_1920x1080_25fps_4000kbps_vp8.webm"},
{"crowd_1920x1080_25fps_6700kbps_h264.ts"},
{"crowd_1920x1080_25fps_4000kbps_h265.mkv"},
{"crowd_1920x1080_25fps_4000kbps_vp9.webm"},
{"crowd_176x144_25fps_6000kbps_mpeg4.mp4"},
{"crowd_176x144_25fps_6000kbps_h263.3gp"}});
}
public EncoderTest(String inputFileName) {
this.mInputFile = inputFileName;
}
@BeforeClass
public static void writeStatsHeaderToFile() throws IOException {
Stats mStats = new Stats();
boolean status = mStats.writeStatsHeader(mStatsFile);
assertTrue("Unable to open stats file for writing!", status);
Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testEncoder() throws Exception {
int status;
int frameSize;
//Parameters for video
int width = 0;
int height = 0;
int profile = 0;
int level = 0;
int frameRate = 0;
//Parameters for audio
int bitRate = 0;
int sampleRate = 0;
int numChannels = 0;
File inputFile = new File(mInputFilePath + mInputFile);
assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
inputFile.exists());
FileInputStream fileInput = new FileInputStream(inputFile);
FileDescriptor fileDescriptor = fileInput.getFD();
Extractor extractor = new Extractor();
int trackCount = extractor.setUpExtractor(fileDescriptor);
assertTrue("Extraction failed. No tracks for file: " + mInputFile, (trackCount > 0));
ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
int colorFormat = COLOR_FormatYUV420Flexible;
extractor.selectExtractorTrack(currentTrack);
MediaFormat format = extractor.getFormat(currentTrack);
// Get samples from extractor
int sampleSize;
do {
sampleSize = extractor.getFrameSample();
MediaCodec.BufferInfo bufInfo = new MediaCodec.BufferInfo();
MediaCodec.BufferInfo info = extractor.getBufferInfo();
ByteBuffer dataBuffer = ByteBuffer.allocate(info.size);
dataBuffer.put(extractor.getFrameBuffer().array(), 0, info.size);
bufInfo.set(info.offset, info.size, info.presentationTimeUs, info.flags);
inputBuffer.add(dataBuffer);
frameInfo.add(bufInfo);
if (DEBUG) {
Log.d(TAG, "Extracted bufInfo: flag = " + bufInfo.flags + " timestamp = " +
bufInfo.presentationTimeUs + " size = " + bufInfo.size);
}
} while (sampleSize > 0);
int tid = android.os.Process.myTid();
File decodedFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
FileOutputStream decodeOutputStream = new FileOutputStream(decodedFile);
Decoder decoder = new Decoder();
decoder.setupDecoder(decodeOutputStream);
status = decoder.decode(inputBuffer, frameInfo, false, format, "");
assertEquals("Decoder returned error " + status + " for file: " + mInputFile, 0,
status);
MediaFormat decoderFormat = decoder.getFormat();
decoder.deInitCodec();
extractor.unselectExtractorTrack(currentTrack);
inputBuffer.clear();
frameInfo.clear();
if (decodeOutputStream != null) {
decodeOutputStream.close();
}
String mime = format.getString(MediaFormat.KEY_MIME);
ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
assertTrue("No suitable codecs found for file: " + mInputFile + " track : " +
currentTrack + " mime: " + mime, (mediaCodecs.size() > 0));
Boolean[] encodeMode = {true, false};
/* Encoding the decoder's output */
for (Boolean asyncMode : encodeMode) {
for (String codecName : mediaCodecs) {
FileOutputStream encodeOutputStream = null;
if (WRITE_OUTPUT) {
File outEncodeFile = new File(mOutputFilePath + "encoder.out");
if (outEncodeFile.exists()) {
assertTrue(" Unable to delete existing file" + outEncodeFile.toString(),
outEncodeFile.delete());
}
assertTrue("Unable to create file to write encoder output: " +
outEncodeFile.toString(), outEncodeFile.createNewFile());
encodeOutputStream = new FileOutputStream(outEncodeFile);
}
File rawFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
assertTrue("Cannot open file to write decoded output", rawFile.exists());
if (DEBUG) {
Log.i(TAG, "Path of decoded input file: " + rawFile.toString());
}
FileInputStream eleStream = new FileInputStream(rawFile);
if (mime.startsWith("video/")) {
width = format.getInteger(MediaFormat.KEY_WIDTH);
height = format.getInteger(MediaFormat.KEY_HEIGHT);
if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
} else if (frameRate <= 0) {
frameRate = ENCODE_DEFAULT_FRAME_RATE;
}
if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE);
} else if (bitRate <= 0) {
if (mime.contains("video/3gpp") || mime.contains("video/mp4v-es")) {
bitRate = ENCODE_MIN_BIT_RATE;
} else {
bitRate = ENCODE_DEFAULT_BIT_RATE;
}
}
if (format.containsKey(MediaFormat.KEY_PROFILE)) {
profile = format.getInteger(MediaFormat.KEY_PROFILE);
}
if (format.containsKey(MediaFormat.KEY_PROFILE)) {
level = format.getInteger(MediaFormat.KEY_LEVEL);
}
if (decoderFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
colorFormat = decoderFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
}
} else {
sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
if (decoderFormat.containsKey(MediaFormat.KEY_BIT_RATE)) {
bitRate = decoderFormat.getInteger(MediaFormat.KEY_BIT_RATE);
} else {
bitRate = ENCODE_DEFAULT_AUDIO_BIT_RATE;
}
}
/*Setup Encode Format*/
MediaFormat encodeFormat;
if (mime.startsWith("video/")) {
frameSize = width * height * 3 / 2;
encodeFormat = MediaFormat.createVideoFormat(mime, width, height);
encodeFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
encodeFormat.setInteger(MediaFormat.KEY_PROFILE, profile);
encodeFormat.setInteger(MediaFormat.KEY_LEVEL, level);
encodeFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, frameSize);
encodeFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
} else {
encodeFormat = MediaFormat.createAudioFormat(mime, sampleRate, numChannels);
encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
frameSize = 4096;
}
Encoder encoder = new Encoder();
encoder.setupEncoder(encodeOutputStream, eleStream);
status = encoder.encode(codecName, encodeFormat, mime, frameRate, sampleRate,
frameSize, asyncMode);
encoder.deInitEncoder();
assertEquals(
codecName + " encoder returned error " + status + " for " + "file:" +
" " + mInputFile, 0, status);
encoder.dumpStatistics(mInputFile, codecName, (asyncMode ? "async" : "sync"),
extractor.getClipDuration(), mStatsFile);
Log.i(TAG, "Encoding complete for file: " + mInputFile + " with codec: " +
codecName + " for aSyncMode = " + asyncMode);
encoder.resetEncoder();
eleStream.close();
if (encodeOutputStream != null) {
encodeOutputStream.close();
}
}
}
//Cleanup temporary input file
if (decodedFile.exists()) {
assertTrue(" Unable to delete decoded file" + decodedFile.toString(),
decodedFile.delete());
Log.i(TAG, "Successfully deleted decoded file");
}
}
extractor.deinitExtractor();
fileInput.close();
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testNativeEncoder() throws Exception {
File inputFile = new File(mInputFilePath + mInputFile);
assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
inputFile.exists());
int tid = android.os.Process.myTid();
final String mDecodedFile = mContext.getFilesDir() + "/decoder_" + tid + ".out";
FileInputStream fileInput = new FileInputStream(inputFile);
FileDescriptor fileDescriptor = fileInput.getFD();
Extractor extractor = new Extractor();
int trackCount = extractor.setUpExtractor(fileDescriptor);
assertTrue("Extraction failed. No tracks for file: ", trackCount > 0);
for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
extractor.selectExtractorTrack(currentTrack);
MediaFormat format = extractor.getFormat(currentTrack);
String mime = format.getString(MediaFormat.KEY_MIME);
ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
// Encoding the decoder's output
for (String codecName : mediaCodecs) {
Native nativeEncoder = new Native();
int status = nativeEncoder
.Encode(mInputFilePath, mInputFile, mDecodedFile, mStatsFile, codecName);
assertEquals(
codecName + " encoder returned error " + status + " for " + "file:" + " " +
mInputFile, 0, status);
}
}
File decodedFile = new File(mDecodedFile);
// Cleanup temporary input file
if (decodedFile.exists()) {
assertTrue("Unable to delete - " + mDecodedFile, decodedFile.delete());
Log.i(TAG, "Successfully deleted - " + mDecodedFile);
}
fileInput.close();
}
}