media: add VideoEncoderTest
This test exercises the range of sizes supported by each video encoder.
This is also a test for asynchronous video decoding and encoding.
Bug: 18513091
Change-Id: Ibacfc37f74ac3fd1ae634ae3de8d2064cff620f3
diff --git a/tests/tests/media/libmediandkjni/Android.mk b/tests/tests/media/libmediandkjni/Android.mk
index 2d2033f..59ff7bb 100644
--- a/tests/tests/media/libmediandkjni/Android.mk
+++ b/tests/tests/media/libmediandkjni/Android.mk
@@ -20,7 +20,9 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := native-media-jni.cpp
+LOCAL_SRC_FILES := \
+ native-media-jni.cpp \
+ codec-utils-jni.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
diff --git a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
new file mode 100644
index 0000000..f99f1c8
--- /dev/null
+++ b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+#undef NDEBUG
+//#define LOG_NDEBUG 0
+#include <stdint.h>
+#include <sys/types.h>
+#include <jni.h>
+
+#include <ScopedLocalRef.h>
+#include <JNIHelp.h>
+
+typedef ssize_t offs_t;
+
+// for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
+#include <android/log.h>
+#define TAG "CodecUtilsJNI"
+#define __ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
+#if LOG_NDEBUG
+#define ALOGV(...) do { if (0) { __ALOGV(__VA_ARGS__); } } while (0)
+#else
+#define ALOGV(...) __ALOGV(__VA_ARGS__)
+#endif
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
+#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
+#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+
+struct NativeImage {
+ struct crop {
+ int left;
+ int top;
+ int right;
+ int bottom;
+ } crop;
+ struct plane {
+ const uint8_t *buffer;
+ size_t size;
+ ssize_t colInc;
+ ssize_t rowInc;
+ offs_t cropOffs;
+ size_t cropWidth;
+ size_t cropHeight;
+ } plane[3];
+ int width;
+ int height;
+ int format;
+ long timestamp;
+ size_t numPlanes;
+};
+
+struct ChecksumAlg {
+ virtual void init() = 0;
+ virtual void update(uint8_t c) = 0;
+ virtual uint32_t checksum() = 0;
+ virtual size_t length() = 0;
+protected:
+ virtual ~ChecksumAlg() {}
+};
+
+struct Adler32 : ChecksumAlg {
+ Adler32() {
+ init();
+ }
+ void init() {
+ a = 1;
+ len = b = 0;
+ }
+ void update(uint8_t c) {
+ a += c;
+ b += a;
+ ++len;
+ }
+ uint32_t checksum() {
+ return (a % 65521) + ((b % 65521) << 16);
+ }
+ size_t length() {
+ return len;
+ }
+private:
+ uint32_t a, b;
+ size_t len;
+};
+
+static struct ImageFieldsAndMethods {
+ // android.graphics.ImageFormat
+ int YUV_420_888;
+ // android.media.Image
+ jmethodID methodWidth;
+ jmethodID methodHeight;
+ jmethodID methodFormat;
+ jmethodID methodTimestamp;
+ jmethodID methodPlanes;
+ jmethodID methodCrop;
+ // android.media.Image.Plane
+ jmethodID methodBuffer;
+ jmethodID methodPixelStride;
+ jmethodID methodRowStride;
+ // android.graphics.Rect
+ jfieldID fieldLeft;
+ jfieldID fieldTop;
+ jfieldID fieldRight;
+ jfieldID fieldBottom;
+} gFields;
+static bool gFieldsInitialized = false;
+
+void initializeGlobalFields(JNIEnv *env) {
+ if (gFieldsInitialized) {
+ return;
+ }
+ { // ImageFormat
+ jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
+ const jfieldID fieldYUV420888 = env->GetStaticFieldID(imageFormatClazz, "YUV_420_888", "I");
+ gFields.YUV_420_888 = env->GetStaticIntField(imageFormatClazz, fieldYUV420888);
+ env->DeleteLocalRef(imageFormatClazz);
+ imageFormatClazz = NULL;
+ }
+
+ { // Image
+ jclass imageClazz = env->FindClass("android/media/Image");
+ gFields.methodWidth = env->GetMethodID(imageClazz, "getWidth", "()I");
+ gFields.methodHeight = env->GetMethodID(imageClazz, "getHeight", "()I");
+ gFields.methodFormat = env->GetMethodID(imageClazz, "getFormat", "()I");
+ gFields.methodTimestamp = env->GetMethodID(imageClazz, "getTimestamp", "()J");
+ gFields.methodPlanes = env->GetMethodID(
+ imageClazz, "getPlanes", "()[Landroid/media/Image$Plane;");
+ gFields.methodCrop = env->GetMethodID(
+ imageClazz, "getCropRect", "()Landroid/graphics/Rect;");
+ env->DeleteLocalRef(imageClazz);
+ imageClazz = NULL;
+ }
+
+ { // Image.Plane
+ jclass planeClazz = env->FindClass("android/media/Image$Plane");
+ gFields.methodBuffer = env->GetMethodID(planeClazz, "getBuffer", "()Ljava/nio/ByteBuffer;");
+ gFields.methodPixelStride = env->GetMethodID(planeClazz, "getPixelStride", "()I");
+ gFields.methodRowStride = env->GetMethodID(planeClazz, "getRowStride", "()I");
+ env->DeleteLocalRef(planeClazz);
+ planeClazz = NULL;
+ }
+
+ { // Rect
+ jclass rectClazz = env->FindClass("android/graphics/Rect");
+ gFields.fieldLeft = env->GetFieldID(rectClazz, "left", "I");
+ gFields.fieldTop = env->GetFieldID(rectClazz, "top", "I");
+ gFields.fieldRight = env->GetFieldID(rectClazz, "right", "I");
+ gFields.fieldBottom = env->GetFieldID(rectClazz, "bottom", "I");
+ env->DeleteLocalRef(rectClazz);
+ rectClazz = NULL;
+ }
+ gFieldsInitialized = true;
+}
+
+NativeImage *getNativeImage(JNIEnv *env, jobject image) {
+ if (image == NULL) {
+ jniThrowNullPointerException(env, "image is null");
+ return NULL;
+ }
+
+ initializeGlobalFields(env);
+
+ NativeImage *img = new NativeImage;
+ img->format = env->CallIntMethod(image, gFields.methodFormat);
+ img->width = env->CallIntMethod(image, gFields.methodWidth);
+ img->height = env->CallIntMethod(image, gFields.methodHeight);
+ img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
+
+ jobject cropRect = env->CallObjectMethod(image, gFields.methodCrop);
+ img->crop.left = env->GetIntField(cropRect, gFields.fieldLeft);
+ img->crop.top = env->GetIntField(cropRect, gFields.fieldTop);
+ img->crop.right = env->GetIntField(cropRect, gFields.fieldRight);
+ img->crop.bottom = env->GetIntField(cropRect, gFields.fieldBottom);
+ if (img->crop.right == 0 && img->crop.bottom == 0) {
+ img->crop.right = img->width;
+ img->crop.bottom = img->height;
+ }
+ env->DeleteLocalRef(cropRect);
+ cropRect = NULL;
+
+ if (img->format != gFields.YUV_420_888) {
+ jniThrowException(
+ env, "java/lang/UnsupportedOperationException",
+ "only support YUV_420_888 images");
+ delete img;
+ img = NULL;
+ return NULL;
+ }
+ img->numPlanes = 3;
+
+ ScopedLocalRef<jobjectArray> planesArray(
+ env, (jobjectArray)env->CallObjectMethod(image, gFields.methodPlanes));
+ int xDecim = 0;
+ int yDecim = 0;
+ for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+ ScopedLocalRef<jobject> plane(
+ env, env->GetObjectArrayElement(planesArray.get(), (jsize)ix));
+ img->plane[ix].colInc = env->CallIntMethod(plane.get(), gFields.methodPixelStride);
+ img->plane[ix].rowInc = env->CallIntMethod(plane.get(), gFields.methodRowStride);
+ ScopedLocalRef<jobject> buffer(
+ env, env->CallObjectMethod(plane.get(), gFields.methodBuffer));
+
+ img->plane[ix].buffer = (const uint8_t *)env->GetDirectBufferAddress(buffer.get());
+ img->plane[ix].size = env->GetDirectBufferCapacity(buffer.get());
+
+ img->plane[ix].cropOffs =
+ (img->crop.left >> xDecim) * img->plane[ix].colInc
+ + (img->crop.top >> yDecim) * img->plane[ix].rowInc;
+ img->plane[ix].cropHeight =
+ ((img->crop.bottom + (1 << yDecim) - 1) >> yDecim) - (img->crop.top >> yDecim);
+ img->plane[ix].cropWidth =
+ ((img->crop.right + (1 << xDecim) - 1) >> xDecim) - (img->crop.left >> xDecim);
+
+ // sanity check on increments
+ ssize_t widthOffs =
+ (((img->width + (1 << xDecim) - 1) >> xDecim) - 1) * img->plane[ix].colInc;
+ ssize_t heightOffs =
+ (((img->height + (1 << yDecim) - 1) >> yDecim) - 1) * img->plane[ix].rowInc;
+ if (widthOffs < 0 || heightOffs < 0
+ || widthOffs + heightOffs >= (ssize_t)img->plane[ix].size) {
+ jniThrowException(
+ env, "java/lang/IndexOutOfBoundsException", "plane exceeds bytearray");
+ delete img;
+ img = NULL;
+ return NULL;
+ }
+ xDecim = yDecim = 1;
+ }
+ return img;
+}
+
+extern "C" jint Java_android_media_cts_CodecUtils_getImageChecksum(JNIEnv *env,
+ jclass /*clazz*/, jobject image)
+{
+ NativeImage *img = getNativeImage(env, image);
+ if (img == NULL) {
+ return 0;
+ }
+
+ Adler32 adler;
+ for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+ const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+ for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+ const uint8_t *col = row;
+ ssize_t colInc = img->plane[ix].colInc;
+ for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+ adler.update(*col);
+ col += colInc;
+ }
+ row += img->plane[ix].rowInc;
+ }
+ }
+ ALOGV("adler %zu/%u", adler.length(), adler.checksum());
+ return adler.checksum();
+}
+
+/* tiled copy that loops around source image boundary */
+extern "C" void Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv *env,
+ jclass /*clazz*/, jobject target, jobject source)
+{
+ NativeImage *tgt = getNativeImage(env, target);
+ NativeImage *src = getNativeImage(env, source);
+ if (tgt != NULL && src != NULL) {
+ ALOGV("copyFlexYUVImage %dx%d (%d,%d..%d,%d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd <= "
+ "%dx%d (%d, %d..%d, %d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd",
+ tgt->width, tgt->height,
+ tgt->crop.left, tgt->crop.top, tgt->crop.right, tgt->crop.bottom,
+ tgt->plane[0].cropWidth, tgt->plane[0].cropHeight,
+ tgt->plane[0].rowInc, tgt->plane[0].colInc,
+ tgt->plane[1].rowInc, tgt->plane[1].colInc,
+ tgt->plane[2].rowInc, tgt->plane[2].colInc,
+ src->width, src->height,
+ src->crop.left, src->crop.top, src->crop.right, src->crop.bottom,
+ src->plane[0].cropWidth, src->plane[0].cropHeight,
+ src->plane[0].rowInc, src->plane[0].colInc,
+ src->plane[1].rowInc, src->plane[1].colInc,
+ src->plane[2].rowInc, src->plane[2].colInc);
+ for (size_t ix = 0; ix < tgt->numPlanes; ++ix) {
+ uint8_t *row = const_cast<uint8_t *>(tgt->plane[ix].buffer) + tgt->plane[ix].cropOffs;
+ for (size_t y = 0; y < tgt->plane[ix].cropHeight; ++y) {
+ uint8_t *col = row;
+ ssize_t colInc = tgt->plane[ix].colInc;
+ const uint8_t *srcRow = (src->plane[ix].buffer + src->plane[ix].cropOffs
+ + src->plane[ix].rowInc * (y % src->plane[ix].cropHeight));
+ for (size_t x = 0; x < tgt->plane[ix].cropWidth; ++x) {
+ *col = srcRow[src->plane[ix].colInc * (x % src->plane[ix].cropWidth)];
+ col += colInc;
+ }
+ row += tgt->plane[ix].rowInc;
+ }
+ }
+ }
+}
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_h264_871kbps_30fps.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_h264_871kbps_30fps.mp4
new file mode 100644
index 0000000..55a83e7
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_mp4_h264_871kbps_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
new file mode 100644
index 0000000..3c3576f
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 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 android.media.cts;
+
+import android.media.Image;
+import android.util.Log;
+
+public class CodecUtils {
+ private static final String TAG = "CodecUtils";
+
+ /** Load jni on initialization */
+ static {
+ Log.i(TAG, "before loadlibrary");
+ System.loadLibrary("ctsmediacodec_jni");
+ Log.i(TAG, "after loadlibrary");
+ }
+
+ public native static int getImageChecksum(Image image);
+ public native static void copyFlexYUVImage(Image target, Image source);
+}
+
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
index fc8ad9c..c87326d 100644
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -70,7 +70,7 @@
eglSetup(width, height);
makeCurrent();
- setup();
+ setup(this);
}
/**
@@ -78,14 +78,18 @@
* new one). Creates a Surface that can be passed to MediaCodec.configure().
*/
public OutputSurface() {
- setup();
+ setup(this);
+ }
+
+ public OutputSurface(final SurfaceTexture.OnFrameAvailableListener listener) {
+ setup(listener);
}
/**
* Creates instances of TextureRender and SurfaceTexture, and a Surface associated
* with the SurfaceTexture.
*/
- private void setup() {
+ private void setup(SurfaceTexture.OnFrameAvailableListener listener) {
mTextureRender = new TextureRender();
mTextureRender.surfaceCreated();
@@ -107,7 +111,7 @@
//
// Java language note: passing "this" out of a constructor is generally unwise,
// but we should be able to get away with it here.
- mSurfaceTexture.setOnFrameAvailableListener(this);
+ mSurfaceTexture.setOnFrameAvailableListener(listener);
mSurface = new Surface(mSurfaceTexture);
}
@@ -285,6 +289,11 @@
mTextureRender.drawFrame(mSurfaceTexture);
}
+ public void latchImage() {
+ mTextureRender.checkGlError("before updateTexImage");
+ mSurfaceTexture.updateTexImage();
+ }
+
@Override
public void onFrameAvailable(SurfaceTexture st) {
if (VERBOSE) Log.d(TAG, "new frame available");
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
new file mode 100644
index 0000000..f78f5f8
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
@@ -0,0 +1,1247 @@
+/*
+ * Copyright 2014 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 android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.media.cts.CodecUtils;
+
+import android.cts.util.MediaUtils;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+public class VideoEncoderTest extends MediaPlayerTestBase {
+ private static final int MAX_SAMPLE_SIZE = 256 * 1024;
+ private static final String TAG = "VideoEncoderTest";
+ private static final long FRAME_TIMEOUT_MS = 1000;
+
+ private static final String SOURCE_URL =
+ "android.resource://com.android.cts.media/raw/video_480x360_mp4_h264_871kbps_30fps";
+
+ private final boolean DEBUG = false;
+
+ abstract class VideoProcessorBase extends MediaCodec.Callback {
+ private static final String TAG = "VideoProcessorBase";
+
+ private MediaExtractor mExtractor;
+ private ByteBuffer mBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+ private int mTrackIndex = -1;
+ private boolean mSignaledDecoderEOS;
+
+ protected boolean mCompleted;
+ protected final Object mCondition = new Object();
+
+ protected MediaFormat mDecFormat;
+ protected MediaCodec mDecoder, mEncoder;
+
+ protected void open(String path) throws IOException {
+ mExtractor = new MediaExtractor();
+ if (path.startsWith("android.resource://")) {
+ mExtractor.setDataSource(mContext, Uri.parse(path), null);
+ } else {
+ mExtractor.setDataSource(path);
+ }
+
+ for (int i = 0; i < mExtractor.getTrackCount(); i++) {
+ MediaFormat fmt = mExtractor.getTrackFormat(i);
+ String mime = fmt.getString(MediaFormat.KEY_MIME).toLowerCase();
+ if (mime.startsWith("video/")) {
+ mTrackIndex = i;
+ mDecFormat = fmt;
+ mExtractor.selectTrack(i);
+ break;
+ }
+ }
+ assertTrue("file " + path + " has no video", mTrackIndex >= 0);
+ }
+
+ // returns true if encoder supports the size
+ protected boolean initCodecsAndConfigureEncoder(
+ String videoEncName, String outMime, int width, int height, int colorFormat)
+ throws IOException {
+ mDecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ String videoDecName = mcl.findDecoderForFormat(mDecFormat);
+ Log.i(TAG, "decoder for " + mDecFormat + " is " + videoDecName);
+ mDecoder = MediaCodec.createByCodecName(videoDecName);
+ mEncoder = MediaCodec.createByCodecName(videoEncName);
+
+ mDecoder.setCallback(this);
+ mEncoder.setCallback(this);
+
+ MediaCodecInfo.VideoCapabilities encCaps =
+ mEncoder.getCodecInfo().getCapabilitiesForType(outMime).getVideoCapabilities();
+ if (!encCaps.isSizeSupported(width, height)) {
+ Log.i(TAG, videoEncName + " does not support size: " + width + "x" + height);
+ return false;
+ }
+
+ MediaFormat outFmt = MediaFormat.createVideoFormat(outMime, width, height);
+
+ {
+ int maxWidth = encCaps.getSupportedWidths().getUpper();
+ int maxHeight = encCaps.getSupportedHeightsFor(maxWidth).getUpper();
+ int maxRate =
+ encCaps.getSupportedFrameRatesFor(maxWidth, maxHeight).getUpper().intValue();
+ outFmt.setInteger(MediaFormat.KEY_FRAME_RATE, Math.min(30, maxRate));
+ int bitRate = encCaps.getBitrateRange().clamp(
+ (int)(encCaps.getBitrateRange().getUpper() /
+ Math.sqrt(maxWidth * maxHeight / width / height)));
+ Log.d(TAG, "max rate = " + maxRate + ", bit rate = " + bitRate);
+ outFmt.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ }
+ outFmt.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+ outFmt.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+ mEncoder.configure(outFmt, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ Log.i(TAG, "encoder input format " + mEncoder.getInputFormat() + " from " + outFmt);
+ return true;
+ }
+
+ protected void close() {
+ if (mDecoder != null) {
+ mDecoder.release();
+ mDecoder = null;
+ }
+ if (mEncoder != null) {
+ mEncoder.release();
+ mEncoder = null;
+ }
+ if (mExtractor != null) {
+ mExtractor.release();
+ mExtractor = null;
+ }
+ }
+
+ // returns true if filled buffer
+ protected boolean fillDecoderInputBuffer(int ix) {
+ if (DEBUG) Log.v(TAG, "decoder received input #" + ix);
+ while (!mSignaledDecoderEOS) {
+ int track = mExtractor.getSampleTrackIndex();
+ if (track >= 0 && track != mTrackIndex) {
+ mExtractor.advance();
+ continue;
+ }
+ int size = mExtractor.readSampleData(mBuffer, 0);
+ if (size < 0) {
+ // queue decoder input EOS
+ if (DEBUG) Log.v(TAG, "queuing decoder EOS");
+ mDecoder.queueInputBuffer(
+ ix, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ mSignaledDecoderEOS = true;
+ } else {
+ mBuffer.limit(size);
+ mBuffer.position(0);
+ BufferInfo info = new BufferInfo();
+ info.set(
+ 0, mBuffer.limit(), mExtractor.getSampleTime(),
+ mExtractor.getSampleFlags());
+ mDecoder.getInputBuffer(ix).put(mBuffer);
+ if (DEBUG) Log.v(TAG, "queing input #" + ix + " for decoder with timestamp "
+ + info.presentationTimeUs);
+ mDecoder.queueInputBuffer(
+ ix, 0, mBuffer.limit(), info.presentationTimeUs, 0);
+ }
+ mExtractor.advance();
+ return true;
+ }
+ return false;
+ }
+
+ protected void emptyEncoderOutputBuffer(int ix, BufferInfo info) {
+ if (DEBUG) Log.v(TAG, "encoder received output #" + ix
+ + " (sz=" + info.size + ", f=" + info.flags
+ + ", ts=" + info.presentationTimeUs + ")");
+ if (!mCompleted) {
+ mEncoder.releaseOutputBuffer(ix, false);
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ Log.d(TAG, "encoder received output EOS");
+ synchronized(mCondition) {
+ mCompleted = true;
+ mCondition.notifyAll(); // condition is always satisfied
+ }
+ }
+ }
+ }
+
+ public abstract boolean processLoop(
+ String path, String outMime, String videoEncName,
+ int width, int height, boolean optional);
+ };
+
+ class VideoProcessor extends VideoProcessorBase {
+ private static final String TAG = "VideoProcessor";
+ private boolean mWorkInProgress;
+ private boolean mGotDecoderEOS;
+ private boolean mSignaledEncoderEOS;
+
+ private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
+ new LinkedList<Pair<Integer, BufferInfo>>();
+ private LinkedList<Integer> mEncInputBuffers = new LinkedList<Integer>();
+
+ private int mEncInputBufferSize = -1;
+
+ @Override
+ public boolean processLoop(
+ String path, String outMime, String videoEncName,
+ int width, int height, boolean optional) {
+ boolean skipped = true;
+ try {
+ open(path);
+ if (!initCodecsAndConfigureEncoder(
+ videoEncName, outMime, width, height,
+ CodecCapabilities.COLOR_FormatYUV420Flexible)) {
+ assertTrue("could not configure encoder for supported size", optional);
+ return !skipped;
+ }
+ skipped = false;
+
+ mDecoder.configure(mDecFormat, null /* surface */, null /* crypto */, 0);
+
+ mDecoder.start();
+ mEncoder.start();
+
+ // main loop - process GL ops as only main thread has GL context
+ while (!mCompleted) {
+ Pair<Integer, BufferInfo> decBuffer = null;
+ int encBuffer = -1;
+ synchronized (mCondition) {
+ try {
+ // wait for an encoder input buffer and a decoder output buffer
+ // Use a timeout to avoid stalling the test if it doesn't arrive.
+ if (!haveBuffers() && !mCompleted) {
+ mCondition.wait(FRAME_TIMEOUT_MS);
+ }
+ } catch (InterruptedException ie) {
+ fail("wait interrupted"); // shouldn't happen
+ }
+ if (mCompleted) {
+ break;
+ }
+ if (!haveBuffers()) {
+ fail("timed out after " + mBuffersToRender.size()
+ + " decoder output and " + mEncInputBuffers.size()
+ + " encoder input buffers");
+ }
+
+ if (DEBUG) Log.v(TAG, "got image");
+ decBuffer = mBuffersToRender.removeFirst();
+ encBuffer = mEncInputBuffers.removeFirst();
+ if (isEOSOnlyBuffer(decBuffer)) {
+ queueEncoderEOS(decBuffer, encBuffer);
+ continue;
+ }
+ mWorkInProgress = true;
+ }
+
+ if (mWorkInProgress) {
+ renderDecodedBuffer(decBuffer, encBuffer);
+ synchronized(mCondition) {
+ mWorkInProgress = false;
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("received exception " + e);
+ } finally {
+ close();
+ }
+ return !skipped;
+ }
+
+ @Override
+ public void onInputBufferAvailable(MediaCodec mediaCodec, int ix) {
+ if (mediaCodec == mDecoder) {
+ // fill input buffer from extractor
+ fillDecoderInputBuffer(ix);
+ } else if (mediaCodec == mEncoder) {
+ synchronized(mCondition) {
+ mEncInputBuffers.addLast(ix);
+ tryToPropagateEOS();
+ if (haveBuffers()) {
+ mCondition.notifyAll();
+ }
+ }
+ } else {
+ fail("received input buffer on " + mediaCodec.getName());
+ }
+ }
+
+ @Override
+ public void onOutputBufferAvailable(
+ MediaCodec mediaCodec, int ix, BufferInfo info) {
+ if (mediaCodec == mDecoder) {
+ if (DEBUG) Log.v(TAG, "decoder received output #" + ix
+ + " (sz=" + info.size + ", f=" + info.flags
+ + ", ts=" + info.presentationTimeUs + ")");
+ // render output buffer from decoder
+ if (!mGotDecoderEOS) {
+ boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ // can release empty buffers now
+ if (info.size == 0) {
+ mDecoder.releaseOutputBuffer(ix, false /* render */);
+ ix = -1; // dummy index used by render to not render
+ }
+ synchronized(mCondition) {
+ if (ix < 0 && eos && mBuffersToRender.size() > 0) {
+ // move lone EOS flag to last buffer to be rendered
+ mBuffersToRender.peekLast().second.flags |=
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ } else if (ix >= 0 || eos) {
+ mBuffersToRender.addLast(Pair.create(ix, info));
+ }
+ if (eos) {
+ tryToPropagateEOS();
+ mGotDecoderEOS = true;
+ }
+ if (haveBuffers()) {
+ mCondition.notifyAll();
+ }
+ }
+ }
+ } else if (mediaCodec == mEncoder) {
+ emptyEncoderOutputBuffer(ix, info);
+ } else {
+ fail("received output buffer on " + mediaCodec.getName());
+ }
+ }
+
+ private void renderDecodedBuffer(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
+ // process heavyweight actions under instance lock
+ Image encImage = mEncoder.getInputImage(encBuffer);
+ Image decImage = mDecoder.getOutputImage(decBuffer.first);
+ assertNotNull("could not get encoder image for " + mEncoder.getInputFormat(), encImage);
+ assertNotNull("could not get decoder image for " + mDecoder.getInputFormat(), decImage);
+ assertEquals("incorrect decoder format",decImage.getFormat(), ImageFormat.YUV_420_888);
+ assertEquals("incorrect encoder format", encImage.getFormat(), ImageFormat.YUV_420_888);
+
+ CodecUtils.copyFlexYUVImage(encImage, decImage);
+
+ // TRICKY: need this for queueBuffer
+ if (mEncInputBufferSize < 0) {
+ mEncInputBufferSize = mEncoder.getInputBuffer(encBuffer).capacity();
+ }
+ Log.d(TAG, "queuing output #" + encBuffer + " for encoder (sz="
+ + mEncInputBufferSize + ", f=" + decBuffer.second.flags
+ + ", ts=" + decBuffer.second.presentationTimeUs + ")");
+ mEncoder.queueInputBuffer(
+ encBuffer, 0, mEncInputBufferSize, decBuffer.second.presentationTimeUs,
+ decBuffer.second.flags);
+ if ((decBuffer.second.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mSignaledEncoderEOS = true;
+ }
+ mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
+ }
+
+ @Override
+ public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
+ fail("received error on " + mediaCodec.getName() + ": " + e);
+ }
+
+ @Override
+ public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
+ Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
+ }
+
+ // next methods are synchronized on mCondition
+ private boolean haveBuffers() {
+ return mEncInputBuffers.size() > 0 && mBuffersToRender.size() > 0
+ && !mSignaledEncoderEOS;
+ }
+
+ private boolean isEOSOnlyBuffer(Pair<Integer, BufferInfo> decBuffer) {
+ return decBuffer.first < 0 || decBuffer.second.size == 0;
+ }
+
+ protected void tryToPropagateEOS() {
+ if (!mWorkInProgress && haveBuffers() && isEOSOnlyBuffer(mBuffersToRender.getFirst())) {
+ Pair<Integer, BufferInfo> decBuffer = mBuffersToRender.removeFirst();
+ int encBuffer = mEncInputBuffers.removeFirst();
+ queueEncoderEOS(decBuffer, encBuffer);
+ }
+ }
+
+ void queueEncoderEOS(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
+ Log.d(TAG, "signaling encoder EOS");
+ mEncoder.queueInputBuffer(encBuffer, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ mSignaledEncoderEOS = true;
+ if (decBuffer.first >= 0) {
+ mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
+ }
+ }
+ }
+
+
+ class SurfaceVideoProcessor extends VideoProcessorBase
+ implements SurfaceTexture.OnFrameAvailableListener {
+ private static final String TAG = "SurfaceVideoProcessor";
+ private boolean mFrameAvailable;
+ private boolean mGotDecoderEOS;
+ private boolean mSignaledEncoderEOS;
+
+ private InputSurface mEncSurface;
+ private OutputSurface mDecSurface;
+ private BufferInfo mInfoOnSurface;
+
+ private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
+ new LinkedList<Pair<Integer, BufferInfo>>();
+
+ @Override
+ public boolean processLoop(
+ String path, String outMime, String videoEncName,
+ int width, int height, boolean optional) {
+ boolean skipped = true;
+ try {
+ open(path);
+ if (!initCodecsAndConfigureEncoder(
+ videoEncName, outMime, width, height,
+ CodecCapabilities.COLOR_FormatSurface)) {
+ assertTrue("could not configure encoder for supported size", optional);
+ return !skipped;
+ }
+ skipped = false;
+
+ mEncSurface = new InputSurface(mEncoder.createInputSurface());
+ mEncSurface.makeCurrent();
+
+ mDecSurface = new OutputSurface(this);
+ //mDecSurface.changeFragmentShader(FRAGMENT_SHADER);
+ mDecoder.configure(mDecFormat, mDecSurface.getSurface(), null /* crypto */, 0);
+
+ mDecoder.start();
+ mEncoder.start();
+
+ // main loop - process GL ops as only main thread has GL context
+ while (!mCompleted) {
+ BufferInfo info = null;
+ synchronized (mCondition) {
+ try {
+ // wait for mFrameAvailable, which is set by onFrameAvailable().
+ // Use a timeout to avoid stalling the test if it doesn't arrive.
+ if (!mFrameAvailable && !mCompleted) {
+ mCondition.wait(FRAME_TIMEOUT_MS);
+ }
+ } catch (InterruptedException ie) {
+ fail("wait interrupted"); // shouldn't happen
+ }
+ if (mCompleted) {
+ break;
+ }
+ assertTrue("still waiting for image", mFrameAvailable);
+ if (DEBUG) Log.v(TAG, "got image");
+ info = mInfoOnSurface;
+ }
+ if (info == null) {
+ continue;
+ }
+ if (info.size > 0) {
+ mDecSurface.latchImage();
+ if (DEBUG) Log.v(TAG, "latched image");
+ mFrameAvailable = false;
+
+ mDecSurface.drawImage();
+ Log.d(TAG, "encoding frame at " + info.presentationTimeUs * 1000);
+
+ mEncSurface.setPresentationTime(info.presentationTimeUs * 1000);
+ mEncSurface.swapBuffers();
+ }
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mSignaledEncoderEOS = true;
+ Log.d(TAG, "signaling encoder EOS");
+ mEncoder.signalEndOfInputStream();
+ }
+
+ synchronized (mCondition) {
+ mInfoOnSurface = null;
+ if (mBuffersToRender.size() > 0 && mInfoOnSurface == null) {
+ if (DEBUG) Log.v(TAG, "handling postponed frame");
+ Pair<Integer, BufferInfo> nextBuffer = mBuffersToRender.removeFirst();
+ renderDecodedBuffer(nextBuffer.first, nextBuffer.second);
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("received exception " + e);
+ } finally {
+ close();
+ if (mEncSurface != null) {
+ mEncSurface.release();
+ mEncSurface = null;
+ }
+ if (mDecSurface != null) {
+ mDecSurface.release();
+ mDecSurface = null;
+ }
+ }
+ return !skipped;
+ }
+
+ @Override
+ public void onFrameAvailable(SurfaceTexture st) {
+ if (DEBUG) Log.v(TAG, "new frame available");
+ synchronized (mCondition) {
+ assertFalse("mFrameAvailable already set, frame could be dropped", mFrameAvailable);
+ mFrameAvailable = true;
+ mCondition.notifyAll();
+ }
+ }
+
+ @Override
+ public void onInputBufferAvailable(MediaCodec mediaCodec, int ix) {
+ if (mediaCodec == mDecoder) {
+ // fill input buffer from extractor
+ fillDecoderInputBuffer(ix);
+ } else {
+ fail("received input buffer on " + mediaCodec.getName());
+ }
+ }
+
+ @Override
+ public void onOutputBufferAvailable(
+ MediaCodec mediaCodec, int ix, BufferInfo info) {
+ if (mediaCodec == mDecoder) {
+ if (DEBUG) Log.v(TAG, "decoder received output #" + ix
+ + " (sz=" + info.size + ", f=" + info.flags
+ + ", ts=" + info.presentationTimeUs + ")");
+ // render output buffer from decoder
+ if (!mGotDecoderEOS) {
+ boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ if (eos) {
+ mGotDecoderEOS = true;
+ }
+ // can release empty buffers now
+ if (info.size == 0) {
+ mDecoder.releaseOutputBuffer(ix, false /* render */);
+ ix = -1; // dummy index used by render to not render
+ }
+ if (eos || info.size > 0) {
+ synchronized(mCondition) {
+ if (mInfoOnSurface != null || mBuffersToRender.size() > 0) {
+ if (DEBUG) Log.v(TAG, "postponing render, surface busy");
+ mBuffersToRender.addLast(Pair.create(ix, info));
+ } else {
+ renderDecodedBuffer(ix, info);
+ }
+ }
+ }
+ }
+ } else if (mediaCodec == mEncoder) {
+ emptyEncoderOutputBuffer(ix, info);
+ } else {
+ fail("received output buffer on " + mediaCodec.getName());
+ }
+ }
+
+ private void renderDecodedBuffer(int ix, BufferInfo info) {
+ boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ mInfoOnSurface = info;
+ if (info.size > 0) {
+ Log.d(TAG, "rendering frame #" + ix + " at " + info.presentationTimeUs * 1000
+ + (eos ? " with EOS" : ""));
+ mDecoder.releaseOutputBuffer(ix, info.presentationTimeUs * 1000);
+ }
+
+ if (eos && info.size == 0) {
+ if (DEBUG) Log.v(TAG, "decoder output EOS available");
+ mFrameAvailable = true;
+ mCondition.notifyAll();
+ }
+ }
+
+ @Override
+ public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
+ fail("received error on " + mediaCodec.getName() + ": " + e);
+ }
+
+ @Override
+ public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
+ Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
+ }
+ }
+
+ class Encoder {
+ final private String mName;
+ final private String mMime;
+ final private VideoCapabilities mCaps;
+
+ final private Map<Size, Set<Size>> mMinMax; // extreme sizes
+ final private Map<Size, Set<Size>> mNearMinMax; // sizes near extreme
+ final private Set<Size> mArbitrary; // arbitrary sizes in the middle
+ final private Set<Size> mSizes; // all non-specifically tested sizes
+
+ final private int xAlign;
+ final private int yAlign;
+
+ Encoder(String name, String mime, CodecCapabilities caps) {
+ mName = name;
+ mMime = mime;
+ mCaps = caps.getVideoCapabilities();
+
+ /* calculate min/max sizes */
+ mMinMax = new HashMap<Size, Set<Size>>();
+ mNearMinMax = new HashMap<Size, Set<Size>>();
+ mArbitrary = new HashSet<Size>();
+ mSizes = new HashSet<Size>();
+
+ xAlign = mCaps.getWidthAlignment();
+ yAlign = mCaps.getHeightAlignment();
+
+ initializeSizes();
+ }
+
+ private void initializeSizes() {
+ for (int x = 0; x < 2; ++x) {
+ for (int y = 0; y < 2; ++y) {
+ addExtremeSizesFor(x, y);
+ }
+ }
+
+ // initialize arbitrary sizes
+ for (int i = 1; i <= 7; ++i) {
+ int j = ((7 * i) % 11) + 1;
+ int width = alignedPointInRange(i * 0.125, xAlign, mCaps.getSupportedWidths());
+ int height = alignedPointInRange(
+ j * 0.077, yAlign, mCaps.getSupportedHeightsFor(width));
+ mArbitrary.add(new Size(width, height));
+
+ height = alignedPointInRange(i * 0.125, yAlign, mCaps.getSupportedHeights());
+ width = alignedPointInRange(j * 0.077, xAlign, mCaps.getSupportedWidthsFor(height));
+ mArbitrary.add(new Size(width, height));
+ }
+ mArbitrary.removeAll(mSizes);
+ mSizes.addAll(mArbitrary);
+ if (DEBUG) Log.i(TAG, "arbitrary=" + mArbitrary);
+ }
+
+ private void addExtremeSizesFor(int x, int y) {
+ Set<Size> minMax = new HashSet<Size>();
+ Set<Size> nearMinMax = new HashSet<Size>();
+
+ for (int dx = 0; dx <= xAlign; dx += xAlign) {
+ for (int dy = 0; dy <= yAlign; dy += yAlign) {
+ Set<Size> bucket = (dx + dy == 0) ? minMax : nearMinMax;
+ try {
+ int width = getExtreme(mCaps.getSupportedWidths(), x, dx);
+ int height = getExtreme(mCaps.getSupportedHeightsFor(width), y, dy);
+ bucket.add(new Size(width, height));
+
+ // try max max with more reasonable ratio if too skewed
+ if (x + y == 2 && width >= 4 * height) {
+ Size wideScreen = getLargestSizeForRatio(16, 9);
+ width = getExtreme(
+ mCaps.getSupportedWidths()
+ .intersect(0, wideScreen.getWidth()), x, dx);
+ height = getExtreme(mCaps.getSupportedHeightsFor(width), y, 0);
+ bucket.add(new Size(width, height));
+ }
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ int height = getExtreme(mCaps.getSupportedHeights(), y, dy);
+ int width = getExtreme(mCaps.getSupportedWidthsFor(height), x, dx);
+ bucket.add(new Size(width, height));
+
+ // try max max with more reasonable ratio if too skewed
+ if (x + y == 2 && height >= 4 * width) {
+ Size wideScreen = getLargestSizeForRatio(9, 16);
+ height = getExtreme(
+ mCaps.getSupportedHeights()
+ .intersect(0, wideScreen.getHeight()), y, dy);
+ width = getExtreme(mCaps.getSupportedWidthsFor(height), x, dx);
+ bucket.add(new Size(width, height));
+ }
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ }
+
+ // keep unique sizes
+ minMax.removeAll(mSizes);
+ mSizes.addAll(minMax);
+ nearMinMax.removeAll(mSizes);
+ mSizes.addAll(nearMinMax);
+
+ mMinMax.put(new Size(x, y), minMax);
+ mNearMinMax.put(new Size(x, y), nearMinMax);
+ if (DEBUG) Log.i(TAG, x + "x" + y + ": minMax=" + mMinMax + ", near=" + mNearMinMax);
+ }
+
+ private int alignInRange(double value, int align, Range<Integer> range) {
+ return range.clamp(align * (int)Math.round(value / align));
+ }
+
+ /* point should be between 0. and 1. */
+ private int alignedPointInRange(double point, int align, Range<Integer> range) {
+ return alignInRange(
+ range.getLower() + point * (range.getUpper() - range.getLower()), align, range);
+ }
+
+ private int getExtreme(Range<Integer> range, int i, int delta) {
+ int dim = i == 1 ? range.getUpper() - delta : range.getLower() + delta;
+ if (delta == 0
+ || (dim > range.getLower() && dim < range.getUpper())) {
+ return dim;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private Size getLargestSizeForRatio(int x, int y) {
+ Range<Integer> widthRange = mCaps.getSupportedWidths();
+ Range<Integer> heightRange = mCaps.getSupportedHeightsFor(widthRange.getUpper());
+ final int xAlign = mCaps.getWidthAlignment();
+ final int yAlign = mCaps.getHeightAlignment();
+
+ // scale by alignment
+ int width = alignInRange(
+ Math.sqrt(widthRange.getUpper() * heightRange.getUpper() * (double)x / y),
+ xAlign, widthRange);
+ int height = alignInRange(
+ width * (double)y / x, yAlign, mCaps.getSupportedHeightsFor(width));
+ return new Size(width, height);
+ }
+
+
+ public boolean testExtreme(int x, int y, boolean flexYUV, boolean near) {
+ boolean skipped = true;
+ for (Size s : (near ? mNearMinMax : mMinMax).get(new Size(x, y))) {
+ if (test(s.getWidth(), s.getHeight(), false /* optional */, flexYUV)) {
+ skipped = false;
+ }
+ }
+ return !skipped;
+ }
+
+ public boolean testArbitrary(boolean flexYUV) {
+ boolean skipped = true;
+ for (Size s : mArbitrary) {
+ if (test(s.getWidth(), s.getHeight(), false /* optional */, flexYUV)) {
+ skipped = false;
+ }
+ }
+ return !skipped;
+ }
+
+ public boolean testSpecific(int width, int height, boolean flexYUV) {
+ // already tested by one of the min/max tests
+ if (mSizes.contains(new Size(width, height))) {
+ return false;
+ }
+ return test(width, height, true /* optional */, flexYUV);
+ }
+
+ private boolean test(int width, int height, boolean optional, boolean flexYUV) {
+ Log.i(TAG, "testing " + mMime + " on " + mName + " for " + width + "x" + height
+ + (flexYUV ? " flexYUV" : " surface"));
+
+ VideoProcessorBase processor =
+ flexYUV ? new VideoProcessor() : new SurfaceVideoProcessor();
+
+ // We are using a resource URL as an example
+ return processor.processLoop(
+ SOURCE_URL, mMime, mName, width, height, optional);
+ }
+
+ }
+
+ private Encoder[] googH265() { return goog(MediaFormat.MIMETYPE_VIDEO_HEVC); }
+ private Encoder[] googH264() { return goog(MediaFormat.MIMETYPE_VIDEO_AVC); }
+ private Encoder[] googH263() { return goog(MediaFormat.MIMETYPE_VIDEO_H263); }
+ private Encoder[] googMpeg4() { return goog(MediaFormat.MIMETYPE_VIDEO_MPEG4); }
+ private Encoder[] googVP8() { return goog(MediaFormat.MIMETYPE_VIDEO_VP8); }
+ private Encoder[] googVP9() { return goog(MediaFormat.MIMETYPE_VIDEO_VP9); }
+
+ private Encoder[] otherH265() { return other(MediaFormat.MIMETYPE_VIDEO_HEVC); }
+ private Encoder[] otherH264() { return other(MediaFormat.MIMETYPE_VIDEO_AVC); }
+ private Encoder[] otherH263() { return other(MediaFormat.MIMETYPE_VIDEO_H263); }
+ private Encoder[] otherMpeg4() { return other(MediaFormat.MIMETYPE_VIDEO_MPEG4); }
+ private Encoder[] otherVP8() { return other(MediaFormat.MIMETYPE_VIDEO_VP8); }
+ private Encoder[] otherVP9() { return other(MediaFormat.MIMETYPE_VIDEO_VP9); }
+
+ private Encoder[] goog(String mime) {
+ return encoders(mime, true /* goog */);
+ }
+
+ private Encoder[] other(String mime) {
+ return encoders(mime, false /* goog */);
+ }
+
+ private Encoder[] encoders(String mime, boolean goog) {
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ ArrayList<Encoder> result = new ArrayList<Encoder>();
+
+ for (MediaCodecInfo info : mcl.getCodecInfos()) {
+ if (!info.isEncoder()
+ || info.getName().toLowerCase().startsWith("omx.google.") != goog) {
+ continue;
+ }
+ try {
+ CodecCapabilities caps = info.getCapabilitiesForType(mime);
+ result.add(new Encoder(info.getName(), mime, caps));
+ } catch (IllegalArgumentException e) { // mime is not supported
+ }
+ }
+ return result.toArray(new Encoder[result.size()]);
+ }
+
+ public void testGoogH265FlexMinMin() { minmin(googH265(), true /* flex */); }
+ public void testGoogH265SurfMinMin() { minmin(googH265(), false /* flex */); }
+ public void testGoogH264FlexMinMin() { minmin(googH264(), true /* flex */); }
+ public void testGoogH264SurfMinMin() { minmin(googH264(), false /* flex */); }
+ public void testGoogH263FlexMinMin() { minmin(googH263(), true /* flex */); }
+ public void testGoogH263SurfMinMin() { minmin(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexMinMin() { minmin(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfMinMin() { minmin(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexMinMin() { minmin(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfMinMin() { minmin(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexMinMin() { minmin(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfMinMin() { minmin(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexMinMin() { minmin(otherH265(), true /* flex */); }
+ public void testOtherH265SurfMinMin() { minmin(otherH265(), false /* flex */); }
+ public void testOtherH264FlexMinMin() { minmin(otherH264(), true /* flex */); }
+ public void testOtherH264SurfMinMin() { minmin(otherH264(), false /* flex */); }
+ public void testOtherH263FlexMinMin() { minmin(otherH263(), true /* flex */); }
+ public void testOtherH263SurfMinMin() { minmin(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexMinMin() { minmin(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfMinMin() { minmin(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexMinMin() { minmin(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfMinMin() { minmin(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexMinMin() { minmin(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfMinMin() { minmin(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexMinMax() { minmax(googH265(), true /* flex */); }
+ public void testGoogH265SurfMinMax() { minmax(googH265(), false /* flex */); }
+ public void testGoogH264FlexMinMax() { minmax(googH264(), true /* flex */); }
+ public void testGoogH264SurfMinMax() { minmax(googH264(), false /* flex */); }
+ public void testGoogH263FlexMinMax() { minmax(googH263(), true /* flex */); }
+ public void testGoogH263SurfMinMax() { minmax(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexMinMax() { minmax(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfMinMax() { minmax(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexMinMax() { minmax(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfMinMax() { minmax(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexMinMax() { minmax(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfMinMax() { minmax(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexMinMax() { minmax(otherH265(), true /* flex */); }
+ public void testOtherH265SurfMinMax() { minmax(otherH265(), false /* flex */); }
+ public void testOtherH264FlexMinMax() { minmax(otherH264(), true /* flex */); }
+ public void testOtherH264SurfMinMax() { minmax(otherH264(), false /* flex */); }
+ public void testOtherH263FlexMinMax() { minmax(otherH263(), true /* flex */); }
+ public void testOtherH263SurfMinMax() { minmax(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexMinMax() { minmax(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfMinMax() { minmax(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexMinMax() { minmax(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfMinMax() { minmax(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexMinMax() { minmax(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfMinMax() { minmax(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexMaxMin() { maxmin(googH265(), true /* flex */); }
+ public void testGoogH265SurfMaxMin() { maxmin(googH265(), false /* flex */); }
+ public void testGoogH264FlexMaxMin() { maxmin(googH264(), true /* flex */); }
+ public void testGoogH264SurfMaxMin() { maxmin(googH264(), false /* flex */); }
+ public void testGoogH263FlexMaxMin() { maxmin(googH263(), true /* flex */); }
+ public void testGoogH263SurfMaxMin() { maxmin(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexMaxMin() { maxmin(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfMaxMin() { maxmin(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexMaxMin() { maxmin(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfMaxMin() { maxmin(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexMaxMin() { maxmin(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfMaxMin() { maxmin(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexMaxMin() { maxmin(otherH265(), true /* flex */); }
+ public void testOtherH265SurfMaxMin() { maxmin(otherH265(), false /* flex */); }
+ public void testOtherH264FlexMaxMin() { maxmin(otherH264(), true /* flex */); }
+ public void testOtherH264SurfMaxMin() { maxmin(otherH264(), false /* flex */); }
+ public void testOtherH263FlexMaxMin() { maxmin(otherH263(), true /* flex */); }
+ public void testOtherH263SurfMaxMin() { maxmin(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexMaxMin() { maxmin(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfMaxMin() { maxmin(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexMaxMin() { maxmin(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfMaxMin() { maxmin(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexMaxMin() { maxmin(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfMaxMin() { maxmin(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexMaxMax() { maxmax(googH265(), true /* flex */); }
+ public void testGoogH265SurfMaxMax() { maxmax(googH265(), false /* flex */); }
+ public void testGoogH264FlexMaxMax() { maxmax(googH264(), true /* flex */); }
+ public void testGoogH264SurfMaxMax() { maxmax(googH264(), false /* flex */); }
+ public void testGoogH263FlexMaxMax() { maxmax(googH263(), true /* flex */); }
+ public void testGoogH263SurfMaxMax() { maxmax(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexMaxMax() { maxmax(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfMaxMax() { maxmax(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexMaxMax() { maxmax(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfMaxMax() { maxmax(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexMaxMax() { maxmax(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfMaxMax() { maxmax(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexMaxMax() { maxmax(otherH265(), true /* flex */); }
+ public void testOtherH265SurfMaxMax() { maxmax(otherH265(), false /* flex */); }
+ public void testOtherH264FlexMaxMax() { maxmax(otherH264(), true /* flex */); }
+ public void testOtherH264SurfMaxMax() { maxmax(otherH264(), false /* flex */); }
+ public void testOtherH263FlexMaxMax() { maxmax(otherH263(), true /* flex */); }
+ public void testOtherH263SurfMaxMax() { maxmax(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexMaxMax() { maxmax(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfMaxMax() { maxmax(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexMaxMax() { maxmax(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfMaxMax() { maxmax(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexMaxMax() { maxmax(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfMaxMax() { maxmax(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexNearMinMin() { nearminmin(googH265(), true /* flex */); }
+ public void testGoogH265SurfNearMinMin() { nearminmin(googH265(), false /* flex */); }
+ public void testGoogH264FlexNearMinMin() { nearminmin(googH264(), true /* flex */); }
+ public void testGoogH264SurfNearMinMin() { nearminmin(googH264(), false /* flex */); }
+ public void testGoogH263FlexNearMinMin() { nearminmin(googH263(), true /* flex */); }
+ public void testGoogH263SurfNearMinMin() { nearminmin(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexNearMinMin() { nearminmin(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfNearMinMin() { nearminmin(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexNearMinMin() { nearminmin(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfNearMinMin() { nearminmin(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexNearMinMin() { nearminmin(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfNearMinMin() { nearminmin(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexNearMinMin() { nearminmin(otherH265(), true /* flex */); }
+ public void testOtherH265SurfNearMinMin() { nearminmin(otherH265(), false /* flex */); }
+ public void testOtherH264FlexNearMinMin() { nearminmin(otherH264(), true /* flex */); }
+ public void testOtherH264SurfNearMinMin() { nearminmin(otherH264(), false /* flex */); }
+ public void testOtherH263FlexNearMinMin() { nearminmin(otherH263(), true /* flex */); }
+ public void testOtherH263SurfNearMinMin() { nearminmin(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexNearMinMin() { nearminmin(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfNearMinMin() { nearminmin(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexNearMinMin() { nearminmin(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfNearMinMin() { nearminmin(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexNearMinMin() { nearminmin(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfNearMinMin() { nearminmin(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexNearMinMax() { nearminmax(googH265(), true /* flex */); }
+ public void testGoogH265SurfNearMinMax() { nearminmax(googH265(), false /* flex */); }
+ public void testGoogH264FlexNearMinMax() { nearminmax(googH264(), true /* flex */); }
+ public void testGoogH264SurfNearMinMax() { nearminmax(googH264(), false /* flex */); }
+ public void testGoogH263FlexNearMinMax() { nearminmax(googH263(), true /* flex */); }
+ public void testGoogH263SurfNearMinMax() { nearminmax(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexNearMinMax() { nearminmax(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfNearMinMax() { nearminmax(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexNearMinMax() { nearminmax(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfNearMinMax() { nearminmax(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexNearMinMax() { nearminmax(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfNearMinMax() { nearminmax(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexNearMinMax() { nearminmax(otherH265(), true /* flex */); }
+ public void testOtherH265SurfNearMinMax() { nearminmax(otherH265(), false /* flex */); }
+ public void testOtherH264FlexNearMinMax() { nearminmax(otherH264(), true /* flex */); }
+ public void testOtherH264SurfNearMinMax() { nearminmax(otherH264(), false /* flex */); }
+ public void testOtherH263FlexNearMinMax() { nearminmax(otherH263(), true /* flex */); }
+ public void testOtherH263SurfNearMinMax() { nearminmax(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexNearMinMax() { nearminmax(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfNearMinMax() { nearminmax(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexNearMinMax() { nearminmax(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfNearMinMax() { nearminmax(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexNearMinMax() { nearminmax(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfNearMinMax() { nearminmax(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexNearMaxMin() { nearmaxmin(googH265(), true /* flex */); }
+ public void testGoogH265SurfNearMaxMin() { nearmaxmin(googH265(), false /* flex */); }
+ public void testGoogH264FlexNearMaxMin() { nearmaxmin(googH264(), true /* flex */); }
+ public void testGoogH264SurfNearMaxMin() { nearmaxmin(googH264(), false /* flex */); }
+ public void testGoogH263FlexNearMaxMin() { nearmaxmin(googH263(), true /* flex */); }
+ public void testGoogH263SurfNearMaxMin() { nearmaxmin(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexNearMaxMin() { nearmaxmin(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfNearMaxMin() { nearmaxmin(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexNearMaxMin() { nearmaxmin(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfNearMaxMin() { nearmaxmin(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexNearMaxMin() { nearmaxmin(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfNearMaxMin() { nearmaxmin(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexNearMaxMin() { nearmaxmin(otherH265(), true /* flex */); }
+ public void testOtherH265SurfNearMaxMin() { nearmaxmin(otherH265(), false /* flex */); }
+ public void testOtherH264FlexNearMaxMin() { nearmaxmin(otherH264(), true /* flex */); }
+ public void testOtherH264SurfNearMaxMin() { nearmaxmin(otherH264(), false /* flex */); }
+ public void testOtherH263FlexNearMaxMin() { nearmaxmin(otherH263(), true /* flex */); }
+ public void testOtherH263SurfNearMaxMin() { nearmaxmin(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexNearMaxMin() { nearmaxmin(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfNearMaxMin() { nearmaxmin(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexNearMaxMin() { nearmaxmin(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfNearMaxMin() { nearmaxmin(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexNearMaxMin() { nearmaxmin(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfNearMaxMin() { nearmaxmin(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexNearMaxMax() { nearmaxmax(googH265(), true /* flex */); }
+ public void testGoogH265SurfNearMaxMax() { nearmaxmax(googH265(), false /* flex */); }
+ public void testGoogH264FlexNearMaxMax() { nearmaxmax(googH264(), true /* flex */); }
+ public void testGoogH264SurfNearMaxMax() { nearmaxmax(googH264(), false /* flex */); }
+ public void testGoogH263FlexNearMaxMax() { nearmaxmax(googH263(), true /* flex */); }
+ public void testGoogH263SurfNearMaxMax() { nearmaxmax(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexNearMaxMax() { nearmaxmax(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfNearMaxMax() { nearmaxmax(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexNearMaxMax() { nearmaxmax(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfNearMaxMax() { nearmaxmax(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexNearMaxMax() { nearmaxmax(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfNearMaxMax() { nearmaxmax(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexNearMaxMax() { nearmaxmax(otherH265(), true /* flex */); }
+ public void testOtherH265SurfNearMaxMax() { nearmaxmax(otherH265(), false /* flex */); }
+ public void testOtherH264FlexNearMaxMax() { nearmaxmax(otherH264(), true /* flex */); }
+ public void testOtherH264SurfNearMaxMax() { nearmaxmax(otherH264(), false /* flex */); }
+ public void testOtherH263FlexNearMaxMax() { nearmaxmax(otherH263(), true /* flex */); }
+ public void testOtherH263SurfNearMaxMax() { nearmaxmax(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexNearMaxMax() { nearmaxmax(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfNearMaxMax() { nearmaxmax(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexNearMaxMax() { nearmaxmax(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfNearMaxMax() { nearmaxmax(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexNearMaxMax() { nearmaxmax(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfNearMaxMax() { nearmaxmax(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexArbitrary() { arbitrary(googH265(), true /* flex */); }
+ public void testGoogH265SurfArbitrary() { arbitrary(googH265(), false /* flex */); }
+ public void testGoogH264FlexArbitrary() { arbitrary(googH264(), true /* flex */); }
+ public void testGoogH264SurfArbitrary() { arbitrary(googH264(), false /* flex */); }
+ public void testGoogH263FlexArbitrary() { arbitrary(googH263(), true /* flex */); }
+ public void testGoogH263SurfArbitrary() { arbitrary(googH263(), false /* flex */); }
+ public void testGoogMpeg4FlexArbitrary() { arbitrary(googMpeg4(), true /* flex */); }
+ public void testGoogMpeg4SurfArbitrary() { arbitrary(googMpeg4(), false /* flex */); }
+ public void testGoogVP8FlexArbitrary() { arbitrary(googVP8(), true /* flex */); }
+ public void testGoogVP8SurfArbitrary() { arbitrary(googVP8(), false /* flex */); }
+ public void testGoogVP9FlexArbitrary() { arbitrary(googVP9(), true /* flex */); }
+ public void testGoogVP9SurfArbitrary() { arbitrary(googVP9(), false /* flex */); }
+
+ public void testOtherH265FlexArbitrary() { arbitrary(otherH265(), true /* flex */); }
+ public void testOtherH265SurfArbitrary() { arbitrary(otherH265(), false /* flex */); }
+ public void testOtherH264FlexArbitrary() { arbitrary(otherH264(), true /* flex */); }
+ public void testOtherH264SurfArbitrary() { arbitrary(otherH264(), false /* flex */); }
+ public void testOtherH263FlexArbitrary() { arbitrary(otherH263(), true /* flex */); }
+ public void testOtherH263SurfArbitrary() { arbitrary(otherH263(), false /* flex */); }
+ public void testOtherMpeg4FlexArbitrary() { arbitrary(otherMpeg4(), true /* flex */); }
+ public void testOtherMpeg4SurfArbitrary() { arbitrary(otherMpeg4(), false /* flex */); }
+ public void testOtherVP8FlexArbitrary() { arbitrary(otherVP8(), true /* flex */); }
+ public void testOtherVP8SurfArbitrary() { arbitrary(otherVP8(), false /* flex */); }
+ public void testOtherVP9FlexArbitrary() { arbitrary(otherVP9(), true /* flex */); }
+ public void testOtherVP9SurfArbitrary() { arbitrary(otherVP9(), false /* flex */); }
+
+ public void testGoogH265FlexQCIF() { specific(googH265(), 176, 144, true /* flex */); }
+ public void testGoogH265SurfQCIF() { specific(googH265(), 176, 144, false /* flex */); }
+ public void testGoogH264FlexQCIF() { specific(googH264(), 176, 144, true /* flex */); }
+ public void testGoogH264SurfQCIF() { specific(googH264(), 176, 144, false /* flex */); }
+ public void testGoogH263FlexQCIF() { specific(googH263(), 176, 144, true /* flex */); }
+ public void testGoogH263SurfQCIF() { specific(googH263(), 176, 144, false /* flex */); }
+ public void testGoogMpeg4FlexQCIF() { specific(googMpeg4(), 176, 144, true /* flex */); }
+ public void testGoogMpeg4SurfQCIF() { specific(googMpeg4(), 176, 144, false /* flex */); }
+ public void testGoogVP8FlexQCIF() { specific(googVP8(), 176, 144, true /* flex */); }
+ public void testGoogVP8SurfQCIF() { specific(googVP8(), 176, 144, false /* flex */); }
+ public void testGoogVP9FlexQCIF() { specific(googVP9(), 176, 144, true /* flex */); }
+ public void testGoogVP9SurfQCIF() { specific(googVP9(), 176, 144, false /* flex */); }
+
+ public void testOtherH265FlexQCIF() { specific(otherH265(), 176, 144, true /* flex */); }
+ public void testOtherH265SurfQCIF() { specific(otherH265(), 176, 144, false /* flex */); }
+ public void testOtherH264FlexQCIF() { specific(otherH264(), 176, 144, true /* flex */); }
+ public void testOtherH264SurfQCIF() { specific(otherH264(), 176, 144, false /* flex */); }
+ public void testOtherH263FlexQCIF() { specific(otherH263(), 176, 144, true /* flex */); }
+ public void testOtherH263SurfQCIF() { specific(otherH263(), 176, 144, false /* flex */); }
+ public void testOtherMpeg4FlexQCIF() { specific(otherMpeg4(), 176, 144, true /* flex */); }
+ public void testOtherMpeg4SurfQCIF() { specific(otherMpeg4(), 176, 144, false /* flex */); }
+ public void testOtherVP8FlexQCIF() { specific(otherVP8(), 176, 144, true /* flex */); }
+ public void testOtherVP8SurfQCIF() { specific(otherVP8(), 176, 144, false /* flex */); }
+ public void testOtherVP9FlexQCIF() { specific(otherVP9(), 176, 144, true /* flex */); }
+ public void testOtherVP9SurfQCIF() { specific(otherVP9(), 176, 144, false /* flex */); }
+
+ public void testGoogH265Flex480p() { specific(googH265(), 720, 480, true /* flex */); }
+ public void testGoogH265Surf480p() { specific(googH265(), 720, 480, false /* flex */); }
+ public void testGoogH264Flex480p() { specific(googH264(), 720, 480, true /* flex */); }
+ public void testGoogH264Surf480p() { specific(googH264(), 720, 480, false /* flex */); }
+ public void testGoogH263Flex480p() { specific(googH263(), 720, 480, true /* flex */); }
+ public void testGoogH263Surf480p() { specific(googH263(), 720, 480, false /* flex */); }
+ public void testGoogMpeg4Flex480p() { specific(googMpeg4(), 720, 480, true /* flex */); }
+ public void testGoogMpeg4Surf480p() { specific(googMpeg4(), 720, 480, false /* flex */); }
+ public void testGoogVP8Flex480p() { specific(googVP8(), 720, 480, true /* flex */); }
+ public void testGoogVP8Surf480p() { specific(googVP8(), 720, 480, false /* flex */); }
+ public void testGoogVP9Flex480p() { specific(googVP9(), 720, 480, true /* flex */); }
+ public void testGoogVP9Surf480p() { specific(googVP9(), 720, 480, false /* flex */); }
+
+ public void testOtherH265Flex480p() { specific(otherH265(), 720, 480, true /* flex */); }
+ public void testOtherH265Surf480p() { specific(otherH265(), 720, 480, false /* flex */); }
+ public void testOtherH264Flex480p() { specific(otherH264(), 720, 480, true /* flex */); }
+ public void testOtherH264Surf480p() { specific(otherH264(), 720, 480, false /* flex */); }
+ public void testOtherH263Flex480p() { specific(otherH263(), 720, 480, true /* flex */); }
+ public void testOtherH263Surf480p() { specific(otherH263(), 720, 480, false /* flex */); }
+ public void testOtherMpeg4Flex480p() { specific(otherMpeg4(), 720, 480, true /* flex */); }
+ public void testOtherMpeg4Surf480p() { specific(otherMpeg4(), 720, 480, false /* flex */); }
+ public void testOtherVP8Flex480p() { specific(otherVP8(), 720, 480, true /* flex */); }
+ public void testOtherVP8Surf480p() { specific(otherVP8(), 720, 480, false /* flex */); }
+ public void testOtherVP9Flex480p() { specific(otherVP9(), 720, 480, true /* flex */); }
+ public void testOtherVP9Surf480p() { specific(otherVP9(), 720, 480, false /* flex */); }
+
+ // even though H.263 and MPEG-4 are not defined for 720p or 1080p
+ // test for it, in case device claims support for it.
+
+ public void testGoogH265Flex720p() { specific(googH265(), 1280, 720, true /* flex */); }
+ public void testGoogH265Surf720p() { specific(googH265(), 1280, 720, false /* flex */); }
+ public void testGoogH264Flex720p() { specific(googH264(), 1280, 720, true /* flex */); }
+ public void testGoogH264Surf720p() { specific(googH264(), 1280, 720, false /* flex */); }
+ public void testGoogH263Flex720p() { specific(googH263(), 1280, 720, true /* flex */); }
+ public void testGoogH263Surf720p() { specific(googH263(), 1280, 720, false /* flex */); }
+ public void testGoogMpeg4Flex720p() { specific(googMpeg4(), 1280, 720, true /* flex */); }
+ public void testGoogMpeg4Surf720p() { specific(googMpeg4(), 1280, 720, false /* flex */); }
+ public void testGoogVP8Flex720p() { specific(googVP8(), 1280, 720, true /* flex */); }
+ public void testGoogVP8Surf720p() { specific(googVP8(), 1280, 720, false /* flex */); }
+ public void testGoogVP9Flex720p() { specific(googVP9(), 1280, 720, true /* flex */); }
+ public void testGoogVP9Surf720p() { specific(googVP9(), 1280, 720, false /* flex */); }
+
+ public void testOtherH265Flex720p() { specific(otherH265(), 1280, 720, true /* flex */); }
+ public void testOtherH265Surf720p() { specific(otherH265(), 1280, 720, false /* flex */); }
+ public void testOtherH264Flex720p() { specific(otherH264(), 1280, 720, true /* flex */); }
+ public void testOtherH264Surf720p() { specific(otherH264(), 1280, 720, false /* flex */); }
+ public void testOtherH263Flex720p() { specific(otherH263(), 1280, 720, true /* flex */); }
+ public void testOtherH263Surf720p() { specific(otherH263(), 1280, 720, false /* flex */); }
+ public void testOtherMpeg4Flex720p() { specific(otherMpeg4(), 1280, 720, true /* flex */); }
+ public void testOtherMpeg4Surf720p() { specific(otherMpeg4(), 1280, 720, false /* flex */); }
+ public void testOtherVP8Flex720p() { specific(otherVP8(), 1280, 720, true /* flex */); }
+ public void testOtherVP8Surf720p() { specific(otherVP8(), 1280, 720, false /* flex */); }
+ public void testOtherVP9Flex720p() { specific(otherVP9(), 1280, 720, true /* flex */); }
+ public void testOtherVP9Surf720p() { specific(otherVP9(), 1280, 720, false /* flex */); }
+
+ public void testGoogH265Flex1080p() { specific(googH265(), 1920, 1080, true /* flex */); }
+ public void testGoogH265Surf1080p() { specific(googH265(), 1920, 1080, false /* flex */); }
+ public void testGoogH264Flex1080p() { specific(googH264(), 1920, 1080, true /* flex */); }
+ public void testGoogH264Surf1080p() { specific(googH264(), 1920, 1080, false /* flex */); }
+ public void testGoogH263Flex1080p() { specific(googH263(), 1920, 1080, true /* flex */); }
+ public void testGoogH263Surf1080p() { specific(googH263(), 1920, 1080, false /* flex */); }
+ public void testGoogMpeg4Flex1080p() { specific(googMpeg4(), 1920, 1080, true /* flex */); }
+ public void testGoogMpeg4Surf1080p() { specific(googMpeg4(), 1920, 1080, false /* flex */); }
+ public void testGoogVP8Flex1080p() { specific(googVP8(), 1920, 1080, true /* flex */); }
+ public void testGoogVP8Surf1080p() { specific(googVP8(), 1920, 1080, false /* flex */); }
+ public void testGoogVP9Flex1080p() { specific(googVP9(), 1920, 1080, true /* flex */); }
+ public void testGoogVP9Surf1080p() { specific(googVP9(), 1920, 1080, false /* flex */); }
+
+ public void testOtherH265Flex1080p() { specific(otherH265(), 1920, 1080, true /* flex */); }
+ public void testOtherH265Surf1080p() { specific(otherH265(), 1920, 1080, false /* flex */); }
+ public void testOtherH264Flex1080p() { specific(otherH264(), 1920, 1080, true /* flex */); }
+ public void testOtherH264Surf1080p() { specific(otherH264(), 1920, 1080, false /* flex */); }
+ public void testOtherH263Flex1080p() { specific(otherH263(), 1920, 1080, true /* flex */); }
+ public void testOtherH263Surf1080p() { specific(otherH263(), 1920, 1080, false /* flex */); }
+ public void testOtherMpeg4Flex1080p() { specific(otherMpeg4(), 1920, 1080, true /* flex */); }
+ public void testOtherMpeg4Surf1080p() { specific(otherMpeg4(), 1920, 1080, false /* flex */); }
+ public void testOtherVP8Flex1080p() { specific(otherVP8(), 1920, 1080, true /* flex */); }
+ public void testOtherVP8Surf1080p() { specific(otherVP8(), 1920, 1080, false /* flex */); }
+ public void testOtherVP9Flex1080p() { specific(otherVP9(), 1920, 1080, true /* flex */); }
+ public void testOtherVP9Surf1080p() { specific(otherVP9(), 1920, 1080, false /* flex */); }
+
+ private void minmin(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 0 /* x */, 0 /* y */, flexYUV, false /* near */);
+ }
+
+ private void minmax(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 0 /* x */, 1 /* y */, flexYUV, false /* near */);
+ }
+
+ private void maxmin(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 1 /* x */, 0 /* y */, flexYUV, false /* near */);
+ }
+
+ private void maxmax(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 1 /* x */, 1 /* y */, flexYUV, false /* near */);
+ }
+
+ private void nearminmin(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 0 /* x */, 0 /* y */, flexYUV, true /* near */);
+ }
+
+ private void nearminmax(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 0 /* x */, 1 /* y */, flexYUV, true /* near */);
+ }
+
+ private void nearmaxmin(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 1 /* x */, 0 /* y */, flexYUV, true /* near */);
+ }
+
+ private void nearmaxmax(Encoder[] encoders, boolean flexYUV) {
+ extreme(encoders, 1 /* x */, 1 /* y */, flexYUV, true /* near */);
+ }
+
+ private void extreme(Encoder[] encoders, int x, int y, boolean flexYUV, boolean near) {
+ boolean skipped = true;
+ if (encoders.length == 0) {
+ MediaUtils.skipTest("no such encoder present");
+ return;
+ }
+ for (Encoder encoder: encoders) {
+ if (encoder.testExtreme(x, y, flexYUV, near)) {
+ skipped = false;
+ }
+ }
+ if (skipped) {
+ MediaUtils.skipTest("duplicate resolution extreme");
+ }
+ }
+
+ private void arbitrary(Encoder[] encoders, boolean flexYUV) {
+ boolean skipped = true;
+ if (encoders.length == 0) {
+ MediaUtils.skipTest("no such encoder present");
+ return;
+ }
+ for (Encoder encoder: encoders) {
+ if (encoder.testArbitrary(flexYUV)) {
+ skipped = false;
+ }
+ }
+ if (skipped) {
+ MediaUtils.skipTest("duplicate resolution");
+ }
+ }
+
+ /* test specific size */
+ private void specific(Encoder[] encoders, int width, int height, boolean flexYUV) {
+ boolean skipped = true;
+ if (encoders.length == 0) {
+ MediaUtils.skipTest("no such encoder present");
+ return;
+ }
+ for (Encoder encoder : encoders) {
+ if (encoder.testSpecific(width, height, flexYUV)) {
+ skipped = false;
+ }
+ }
+ if (skipped) {
+ MediaUtils.skipTest("duplicate or unsupported resolution");
+ }
+ }
+}