| /* |
| * 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 android.mediav2.common.cts; |
| |
| import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing; |
| import static android.media.MediaCodecInfo.CodecProfileLevel.*; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import android.content.Context; |
| import android.hardware.display.DisplayManager; |
| import android.media.MediaCodec; |
| import android.media.MediaCodecInfo; |
| import android.media.MediaCodecInfo.CodecCapabilities; |
| import android.media.MediaCodecInfo.CodecProfileLevel; |
| import android.media.MediaCodecList; |
| import android.media.MediaFormat; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.PersistableBundle; |
| import android.os.SystemProperties; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.view.Display; |
| import android.view.Surface; |
| |
| import androidx.test.platform.app.InstrumentationRegistry; |
| |
| import com.android.compatibility.common.util.ApiLevelUtil; |
| import com.android.compatibility.common.util.MediaUtils; |
| |
| import org.junit.After; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.rules.TestName; |
| |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.IntStream; |
| |
| /** |
| * This class comprises of routines that are generic to media codec component trying and testing. |
| * <p> |
| * A media codec component essentially processes input data to generate output data. The |
| * component uses a set of input and output buffers. At a simplistic level, the client requests |
| * (or receive) an empty input buffer, fills it up with data and sends it to the codec for |
| * processing. The codec uses up the data and transforms it into one of its empty output |
| * buffers. Finally, the client asks (or receive) a filled output buffer, consume its contents and |
| * release it back to the codec. |
| * <p> |
| * The type of data that a component receives and sends is dependent on the component. For |
| * instance video encoders expect raw yuv/rgb data and send compressed data. A video decoder |
| * receives compressed access-unit and sends reconstructed yuv. But the processes surrounding |
| * this enqueue and dequeue remain common to all components. Operations like configure, requesting |
| * the component for an empty input buffer, feeding the component a filled input buffer, |
| * requesting the component for an output buffer, releasing the processed output buffer, Sending |
| * state transition commands, waiting on component to send all outputs, closing the component and |
| * releasing the resources, ... remain more or less identical to all components. The routines |
| * that manage these generic processes are maintained by this class. Not only the methods that |
| * are responsible for component trying but also methods that test its functionality are covered |
| * here. A video component is expected to give same number of outputs as inputs. The output |
| * buffer timestamps of an audio component or a decoder component is expected to be strictly |
| * increasing. The routines that enforce these generic rules of all components at all times are |
| * part of this class. Besides these, mediaType specific constants, helper utilities to test |
| * specific components or mediaTypes are covered here. |
| * <p> |
| * enqueueInput and dequeueOutput are routines responsible for filling the input buffer and |
| * handing the received output buffer respectively. These shall be component specific, hence they |
| * are abstract methods. Any client intending to use this class shall implement these methods |
| * basing on the component under test. |
| * <p> |
| * In simple terms, the CodecTestBase is a wrapper class comprising generic routines for |
| * component trying and testing. |
| */ |
| public abstract class CodecTestBase { |
| public static final boolean IS_Q = ApiLevelUtil.getApiLevel() == Build.VERSION_CODES.Q; |
| public static final boolean IS_AT_LEAST_R = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R); |
| public static final boolean IS_AT_LEAST_T = |
| ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU); |
| //TODO(b/248315681) Remove codenameEquals() check once devices return correct version for U |
| public static final boolean IS_AT_LEAST_U = ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU) |
| || ApiLevelUtil.codenameEquals("UpsideDownCake"); |
| public static final boolean IS_BEFORE_U = !IS_AT_LEAST_U; |
| public static final boolean FIRST_SDK_IS_AT_LEAST_T = |
| ApiLevelUtil.isFirstApiAtLeast(Build.VERSION_CODES.TIRAMISU); |
| public static final boolean VNDK_IS_AT_LEAST_T = |
| SystemProperties.getInt("ro.vndk.version", Build.VERSION_CODES.CUR_DEVELOPMENT) |
| >= Build.VERSION_CODES.TIRAMISU; |
| public static final boolean IS_HDR_EDITING_SUPPORTED; |
| private static final String LOG_TAG = CodecTestBase.class.getSimpleName(); |
| |
| public static final ArrayList<String> HDR_INFO_IN_BITSTREAM_CODECS = new ArrayList<>(); |
| public static final String HDR_STATIC_INFO = |
| "00 d0 84 80 3e c2 33 c4 86 4c 1d b8 0b 13 3d 42 40 a0 0f 32 00 10 27 df 0d"; |
| public static final String HDR_STATIC_INCORRECT_INFO = |
| "00 d0 84 80 3e c2 33 c4 86 10 27 d0 07 13 3d 42 40 a0 0f 32 00 10 27 df 0d"; |
| public static final HashMap<Integer, String> HDR_DYNAMIC_INFO = new HashMap<>(); |
| public static final HashMap<Integer, String> HDR_DYNAMIC_INCORRECT_INFO = new HashMap<>(); |
| public static final String CODEC_PREFIX_KEY = "codec-prefix"; |
| public static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix"; |
| public static final String MIME_SEL_KEY = "mime-sel"; |
| public static final Map<String, String> CODEC_SEL_KEY_MIME_MAP = new HashMap<>(); |
| public static final Map<String, String> DEFAULT_ENCODERS = new HashMap<>(); |
| public static final Map<String, String> DEFAULT_DECODERS = new HashMap<>(); |
| public static final HashMap<String, int[]> PROFILE_MAP = new HashMap<>(); |
| public static final HashMap<String, int[]> PROFILE_SDR_MAP = new HashMap<>(); |
| public static final HashMap<String, int[]> PROFILE_HLG_MAP = new HashMap<>(); |
| public static final HashMap<String, int[]> PROFILE_HDR10_MAP = new HashMap<>(); |
| public static final HashMap<String, int[]> PROFILE_HDR10_PLUS_MAP = new HashMap<>(); |
| public static final HashMap<String, int[]> PROFILE_HDR_MAP = new HashMap<>(); |
| public static final boolean ENABLE_LOGS = false; |
| public static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000; |
| public static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000; |
| public static final int UNSPECIFIED = 0; |
| // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h |
| // block at most 5ms while looking for io buffers |
| public static final long Q_DEQ_TIMEOUT_US = 5000; |
| // max poll counter before test aborts and returns error |
| public static final int RETRY_LIMIT = 100; |
| public static final String INVALID_CODEC = "unknown.codec_"; |
| static final int[] MPEG2_PROFILES = new int[]{MPEG2ProfileSimple, MPEG2ProfileMain, |
| MPEG2Profile422, MPEG2ProfileSNR, MPEG2ProfileSpatial, MPEG2ProfileHigh}; |
| static final int[] MPEG4_PROFILES = new int[]{MPEG4ProfileSimple, MPEG4ProfileSimpleScalable, |
| MPEG4ProfileCore, MPEG4ProfileMain, MPEG4ProfileNbit, MPEG4ProfileScalableTexture, |
| MPEG4ProfileSimpleFace, MPEG4ProfileSimpleFBA, MPEG4ProfileBasicAnimated, |
| MPEG4ProfileHybrid, MPEG4ProfileAdvancedRealTime, MPEG4ProfileCoreScalable, |
| MPEG4ProfileAdvancedCoding, MPEG4ProfileAdvancedCore, MPEG4ProfileAdvancedScalable, |
| MPEG4ProfileAdvancedSimple}; |
| static final int[] H263_PROFILES = new int[]{H263ProfileBaseline, H263ProfileH320Coding, |
| H263ProfileBackwardCompatible, H263ProfileISWV2, H263ProfileISWV3, |
| H263ProfileHighCompression, H263ProfileInternet, H263ProfileInterlace, |
| H263ProfileHighLatency}; |
| static final int[] VP8_PROFILES = new int[]{VP8ProfileMain}; |
| static final int[] AVC_SDR_PROFILES = new int[]{AVCProfileBaseline, AVCProfileMain, |
| AVCProfileExtended, AVCProfileHigh, AVCProfileConstrainedBaseline, |
| AVCProfileConstrainedHigh}; |
| static final int[] AVC_HLG_PROFILES = new int[]{AVCProfileHigh10}; |
| static final int[] AVC_HDR_PROFILES = AVC_HLG_PROFILES; |
| static final int[] AVC_PROFILES = combine(AVC_SDR_PROFILES, AVC_HDR_PROFILES); |
| static final int[] VP9_SDR_PROFILES = new int[]{VP9Profile0}; |
| static final int[] VP9_HLG_PROFILES = new int[]{VP9Profile2}; |
| static final int[] VP9_HDR10_PROFILES = new int[]{VP9Profile2HDR}; |
| static final int[] VP9_HDR10_PLUS_PROFILES = new int[]{VP9Profile2HDR10Plus}; |
| static final int[] VP9_HDR_PROFILES = |
| combine(VP9_HLG_PROFILES, combine(VP9_HDR10_PROFILES, VP9_HDR10_PLUS_PROFILES)); |
| static final int[] VP9_PROFILES = combine(VP9_SDR_PROFILES, VP9_HDR_PROFILES); |
| static final int[] HEVC_SDR_PROFILES = new int[]{HEVCProfileMain, HEVCProfileMainStill}; |
| static final int[] HEVC_HLG_PROFILES = new int[]{HEVCProfileMain10}; |
| static final int[] HEVC_HDR10_PROFILES = new int[]{HEVCProfileMain10HDR10}; |
| static final int[] HEVC_HDR10_PLUS_PROFILES = new int[]{HEVCProfileMain10HDR10Plus}; |
| static final int[] HEVC_HDR_PROFILES = |
| combine(HEVC_HLG_PROFILES, combine(HEVC_HDR10_PROFILES, HEVC_HDR10_PLUS_PROFILES)); |
| static final int[] HEVC_PROFILES = combine(HEVC_SDR_PROFILES, HEVC_HDR_PROFILES); |
| static final int[] AV1_SDR_PROFILES = new int[]{AV1ProfileMain8}; |
| static final int[] AV1_HLG_PROFILES = new int[]{AV1ProfileMain10}; |
| static final int[] AV1_HDR10_PROFILES = new int[]{AV1ProfileMain10HDR10}; |
| static final int[] AV1_HDR10_PLUS_PROFILES = new int[]{AV1ProfileMain10HDR10Plus}; |
| static final int[] AV1_HDR_PROFILES = |
| combine(AV1_HLG_PROFILES, combine(AV1_HDR10_PROFILES, AV1_HDR10_PLUS_PROFILES)); |
| static final int[] AV1_PROFILES = combine(AV1_SDR_PROFILES, AV1_HDR_PROFILES); |
| static final int[] AAC_PROFILES = new int[]{AACObjectMain, AACObjectLC, AACObjectSSR, |
| AACObjectLTP, AACObjectHE, AACObjectScalable, AACObjectERLC, AACObjectERScalable, |
| AACObjectLD, AACObjectELD, AACObjectXHE}; |
| public static final Context CONTEXT = |
| InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| public static String mimeSelKeys; |
| public static String codecPrefix; |
| public static String mediaTypePrefix; |
| |
| public enum SupportClass { |
| CODEC_ALL, // All codecs must support |
| CODEC_ANY, // At least one codec must support |
| CODEC_DEFAULT, // Default codec must support |
| CODEC_OPTIONAL; // Codec support is optional |
| |
| public static String toString(SupportClass supportRequirements) { |
| switch (supportRequirements) { |
| case CODEC_ALL: |
| return "CODEC_ALL"; |
| case CODEC_ANY: |
| return "CODEC_ANY"; |
| case CODEC_DEFAULT: |
| return "CODEC_DEFAULT"; |
| case CODEC_OPTIONAL: |
| return "CODEC_OPTIONAL"; |
| default: |
| return "Unknown support class"; |
| } |
| } |
| } |
| |
| public enum ComponentClass { |
| ALL, |
| SOFTWARE, |
| HARDWARE; |
| |
| public static String toString(ComponentClass selectSwitch) { |
| switch (selectSwitch) { |
| case ALL: |
| return "all"; |
| case SOFTWARE: |
| return "software only"; |
| case HARDWARE: |
| return "hardware accelerated"; |
| default: |
| return "Unknown select switch"; |
| } |
| } |
| } |
| |
| public CodecTestBase(String encoder, String mime, String allTestParams) { |
| mCodecName = encoder; |
| mMime = mime; |
| mAllTestParams = allTestParams; |
| mAsyncHandle = new CodecAsyncHandler(); |
| mIsAudio = mMime.startsWith("audio/"); |
| mIsVideo = mMime.startsWith("video/"); |
| } |
| |
| protected final String mCodecName; |
| protected final String mMime; |
| protected final String mAllTestParams; // logging |
| |
| protected final boolean mIsAudio; |
| protected final boolean mIsVideo; |
| protected final CodecAsyncHandler mAsyncHandle; |
| protected boolean mIsCodecInAsyncMode; |
| protected boolean mSawInputEOS; |
| protected boolean mSawOutputEOS; |
| protected boolean mSignalEOSWithLastFrame; |
| protected int mInputCount; |
| protected int mOutputCount; |
| protected long mPrevOutputPts; |
| protected boolean mSignalledOutFormatChanged; |
| protected MediaFormat mOutFormat; |
| |
| protected StringBuilder mTestConfig = new StringBuilder(); |
| protected StringBuilder mTestEnv = new StringBuilder(); |
| |
| protected boolean mSaveToMem; |
| protected OutputManager mOutputBuff; |
| |
| protected MediaCodec mCodec; |
| protected Surface mSurface; |
| // NOTE: mSurface is a place holder of current Surface used by the CodecTestBase. |
| // This doesn't own the handle. The ownership with instances that manage |
| // SurfaceView or TextureView, ... They hold the responsibility of calling release(). |
| protected CodecTestActivity mActivity; |
| |
| public static final MediaCodecList MEDIA_CODEC_LIST_ALL; |
| public static final MediaCodecList MEDIA_CODEC_LIST_REGULAR; |
| static { |
| MEDIA_CODEC_LIST_ALL = new MediaCodecList(MediaCodecList.ALL_CODECS); |
| MEDIA_CODEC_LIST_REGULAR = new MediaCodecList(MediaCodecList.REGULAR_CODECS); |
| IS_HDR_EDITING_SUPPORTED = isHDREditingSupported(); |
| CODEC_SEL_KEY_MIME_MAP.put("vp8", MediaFormat.MIMETYPE_VIDEO_VP8); |
| CODEC_SEL_KEY_MIME_MAP.put("vp9", MediaFormat.MIMETYPE_VIDEO_VP9); |
| CODEC_SEL_KEY_MIME_MAP.put("av1", MediaFormat.MIMETYPE_VIDEO_AV1); |
| CODEC_SEL_KEY_MIME_MAP.put("avc", MediaFormat.MIMETYPE_VIDEO_AVC); |
| CODEC_SEL_KEY_MIME_MAP.put("hevc", MediaFormat.MIMETYPE_VIDEO_HEVC); |
| CODEC_SEL_KEY_MIME_MAP.put("mpeg4", MediaFormat.MIMETYPE_VIDEO_MPEG4); |
| CODEC_SEL_KEY_MIME_MAP.put("h263", MediaFormat.MIMETYPE_VIDEO_H263); |
| CODEC_SEL_KEY_MIME_MAP.put("mpeg2", MediaFormat.MIMETYPE_VIDEO_MPEG2); |
| CODEC_SEL_KEY_MIME_MAP.put("vraw", MediaFormat.MIMETYPE_VIDEO_RAW); |
| CODEC_SEL_KEY_MIME_MAP.put("amrnb", MediaFormat.MIMETYPE_AUDIO_AMR_NB); |
| CODEC_SEL_KEY_MIME_MAP.put("amrwb", MediaFormat.MIMETYPE_AUDIO_AMR_WB); |
| CODEC_SEL_KEY_MIME_MAP.put("mp3", MediaFormat.MIMETYPE_AUDIO_MPEG); |
| CODEC_SEL_KEY_MIME_MAP.put("aac", MediaFormat.MIMETYPE_AUDIO_AAC); |
| CODEC_SEL_KEY_MIME_MAP.put("vorbis", MediaFormat.MIMETYPE_AUDIO_VORBIS); |
| CODEC_SEL_KEY_MIME_MAP.put("opus", MediaFormat.MIMETYPE_AUDIO_OPUS); |
| CODEC_SEL_KEY_MIME_MAP.put("g711alaw", MediaFormat.MIMETYPE_AUDIO_G711_ALAW); |
| CODEC_SEL_KEY_MIME_MAP.put("g711mlaw", MediaFormat.MIMETYPE_AUDIO_G711_MLAW); |
| CODEC_SEL_KEY_MIME_MAP.put("araw", MediaFormat.MIMETYPE_AUDIO_RAW); |
| CODEC_SEL_KEY_MIME_MAP.put("flac", MediaFormat.MIMETYPE_AUDIO_FLAC); |
| CODEC_SEL_KEY_MIME_MAP.put("gsm", MediaFormat.MIMETYPE_AUDIO_MSGSM); |
| |
| android.os.Bundle args = InstrumentationRegistry.getArguments(); |
| mimeSelKeys = args.getString(MIME_SEL_KEY); |
| codecPrefix = args.getString(CODEC_PREFIX_KEY); |
| mediaTypePrefix = args.getString(MEDIA_TYPE_PREFIX_KEY); |
| |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_SDR_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_SDR_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_H263, H263_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_MPEG2, MPEG2_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_MPEG4, MPEG4_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP8, VP8_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_SDR_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_SDR_PROFILES); |
| PROFILE_SDR_MAP.put(MediaFormat.MIMETYPE_AUDIO_AAC, AAC_PROFILES); |
| |
| PROFILE_HLG_MAP.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_HLG_PROFILES); |
| PROFILE_HLG_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HLG_PROFILES); |
| PROFILE_HLG_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HLG_PROFILES); |
| PROFILE_HLG_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HLG_PROFILES); |
| |
| PROFILE_HDR10_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR10_PROFILES); |
| PROFILE_HDR10_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR10_PROFILES); |
| PROFILE_HDR10_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR10_PROFILES); |
| |
| PROFILE_HDR10_PLUS_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR10_PLUS_PROFILES); |
| PROFILE_HDR10_PLUS_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR10_PLUS_PROFILES); |
| PROFILE_HDR10_PLUS_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR10_PLUS_PROFILES); |
| |
| PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_HDR_PROFILES); |
| PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR_PROFILES); |
| PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR_PROFILES); |
| PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR_PROFILES); |
| |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_H263, H263_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_MPEG2, MPEG2_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_MPEG4, MPEG4_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP8, VP8_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILES); |
| PROFILE_MAP.put(MediaFormat.MIMETYPE_AUDIO_AAC, AAC_PROFILES); |
| |
| HDR_INFO_IN_BITSTREAM_CODECS.add(MediaFormat.MIMETYPE_VIDEO_AV1); |
| HDR_INFO_IN_BITSTREAM_CODECS.add(MediaFormat.MIMETYPE_VIDEO_AVC); |
| HDR_INFO_IN_BITSTREAM_CODECS.add(MediaFormat.MIMETYPE_VIDEO_HEVC); |
| |
| HDR_DYNAMIC_INFO.put(0, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0a 00 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 00"); |
| HDR_DYNAMIC_INFO.put(4, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0a 00 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 00"); |
| HDR_DYNAMIC_INFO.put(12, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0e 80 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 00"); |
| HDR_DYNAMIC_INFO.put(22, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0e 80 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 00"); |
| |
| HDR_DYNAMIC_INCORRECT_INFO.put(0, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0a 00 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 00"); |
| HDR_DYNAMIC_INCORRECT_INFO.put(4, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0a 00 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 01"); |
| HDR_DYNAMIC_INCORRECT_INFO.put(12, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0e 80 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 02"); |
| HDR_DYNAMIC_INCORRECT_INFO.put(22, "b5 00 3c 00 01 04 00 40 00 0c 80 4e 20 27 10 00" |
| + "0e 80 00 24 08 00 00 28 00 00 50 00 28 c8 00 c9" |
| + "90 02 aa 58 05 ca d0 0c 0a f8 16 83 18 9c 18 00" |
| + "40 78 13 64 d5 7c 2e 2c c3 59 de 79 6e c3 c2 03"); |
| } |
| |
| static int[] combine(int[] first, int[] second) { |
| int[] result = Arrays.copyOf(first, first.length + second.length); |
| System.arraycopy(second, 0, result, first.length, second.length); |
| return result; |
| } |
| |
| public static boolean isMediaTypeLossless(String mediaType) { |
| if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) return true; |
| if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_RAW)) return true; |
| return false; |
| } |
| |
| // some media types decode a pre-roll amount before playback. This would mean that decoding |
| // after seeking may not return the exact same values as would be obtained by decoding the |
| // stream straight through |
| public static boolean isMediaTypeOutputUnAffectedBySeek(String mediaType) { |
| if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) return true; |
| if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_RAW)) return true; |
| if (mediaType.startsWith("video/")) return true; |
| return false; |
| } |
| |
| public static boolean hasDecoder(String mime) { |
| return CodecTestBase.selectCodecs(mime, null, null, false).size() != 0; |
| } |
| |
| public static boolean hasEncoder(String mime) { |
| return CodecTestBase.selectCodecs(mime, null, null, true).size() != 0; |
| } |
| |
| public static void checkFormatSupport(String codecName, String mime, boolean isEncoder, |
| ArrayList<MediaFormat> formats, String[] features, SupportClass supportRequirements) |
| throws IOException { |
| if (!areFormatsSupported(codecName, mime, formats)) { |
| switch (supportRequirements) { |
| case CODEC_ALL: |
| fail("format(s) not supported by codec: " + codecName + " for mime : " + mime |
| + " formats: " + formats); |
| break; |
| case CODEC_ANY: |
| if (selectCodecs(mime, formats, features, isEncoder).isEmpty()) { |
| fail("format(s) not supported by any component for mime : " + mime |
| + " formats: " + formats); |
| } |
| break; |
| case CODEC_DEFAULT: |
| if (isDefaultCodec(codecName, mime, isEncoder)) { |
| fail("format(s) not supported by default codec : " + codecName |
| + "for mime : " + mime + " formats: " + formats); |
| } |
| break; |
| case CODEC_OPTIONAL: |
| default: |
| // the later assumeTrue() ensures we skip the test for unsupported codecs |
| break; |
| } |
| Assume.assumeTrue("format(s) not supported by codec: " + codecName + " for mime : " |
| + mime, false); |
| } |
| } |
| |
| public static boolean isFeatureSupported(String name, String mime, String feature) |
| throws IOException { |
| MediaCodec codec = MediaCodec.createByCodecName(name); |
| MediaCodecInfo.CodecCapabilities codecCapabilities = |
| codec.getCodecInfo().getCapabilitiesForType(mime); |
| boolean isSupported = codecCapabilities.isFeatureSupported(feature); |
| codec.release(); |
| return isSupported; |
| } |
| |
| public static boolean isHDREditingSupported() { |
| for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_REGULAR.getCodecInfos()) { |
| if (!codecInfo.isEncoder()) { |
| continue; |
| } |
| for (String mediaType : codecInfo.getSupportedTypes()) { |
| CodecCapabilities caps = codecInfo.getCapabilitiesForType(mediaType); |
| if (caps != null && caps.isFeatureSupported(FEATURE_HdrEditing)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static boolean doesAnyFormatHaveHDRProfile(String mime, ArrayList<MediaFormat> formats) { |
| int[] profileArray = PROFILE_HDR_MAP.get(mime); |
| if (profileArray != null) { |
| for (MediaFormat format : formats) { |
| assertEquals(mime, format.getString(MediaFormat.KEY_MIME)); |
| int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1); |
| if (IntStream.of(profileArray).anyMatch(x -> x == profile)) return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean doesCodecSupportHDRProfile(String codecName, String mediaType) { |
| int[] hdrProfiles = PROFILE_HDR_MAP.get(mediaType); |
| if (hdrProfiles == null) { |
| return false; |
| } |
| for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_REGULAR.getCodecInfos()) { |
| if (!codecName.equals(codecInfo.getName())) { |
| continue; |
| } |
| CodecCapabilities caps = codecInfo.getCapabilitiesForType(mediaType); |
| if (caps == null) { |
| return false; |
| } |
| for (CodecProfileLevel pl : caps.profileLevels) { |
| if (IntStream.of(hdrProfiles).anyMatch(x -> x == pl.profile)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static boolean canDisplaySupportHDRContent() { |
| DisplayManager displayManager = CONTEXT.getSystemService(DisplayManager.class); |
| return displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities() |
| .getSupportedHdrTypes().length != 0; |
| } |
| |
| public static boolean areFormatsSupported(String name, String mime, |
| ArrayList<MediaFormat> formats) throws IOException { |
| MediaCodec codec = MediaCodec.createByCodecName(name); |
| MediaCodecInfo.CodecCapabilities codecCapabilities = |
| codec.getCodecInfo().getCapabilitiesForType(mime); |
| boolean isSupported = true; |
| if (formats != null) { |
| for (int i = 0; i < formats.size() && isSupported; i++) { |
| isSupported = codecCapabilities.isFormatSupported(formats.get(i)); |
| } |
| } |
| codec.release(); |
| return isSupported; |
| } |
| |
| public static boolean hasSupportForColorFormat(String name, String mime, int colorFormat) |
| throws IOException { |
| MediaCodec codec = MediaCodec.createByCodecName(name); |
| MediaCodecInfo.CodecCapabilities cap = |
| codec.getCodecInfo().getCapabilitiesForType(mime); |
| boolean hasSupport = false; |
| for (int c : cap.colorFormats) { |
| if (c == colorFormat) { |
| hasSupport = true; |
| break; |
| } |
| } |
| codec.release(); |
| return hasSupport; |
| } |
| |
| public static boolean isDefaultCodec(String codecName, String mime, boolean isEncoder) |
| throws IOException { |
| Map<String, String> mDefaultCodecs = isEncoder ? DEFAULT_ENCODERS : DEFAULT_DECODERS; |
| if (mDefaultCodecs.containsKey(mime)) { |
| return mDefaultCodecs.get(mime).equalsIgnoreCase(codecName); |
| } |
| MediaCodec codec = isEncoder ? MediaCodec.createEncoderByType(mime) |
| : MediaCodec.createDecoderByType(mime); |
| boolean isDefault = codec.getName().equalsIgnoreCase(codecName); |
| mDefaultCodecs.put(mime, codec.getName()); |
| codec.release(); |
| return isDefault; |
| } |
| |
| public static boolean isVendorCodec(String codecName) { |
| for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_ALL.getCodecInfos()) { |
| if (codecName.equals(codecInfo.getName())) { |
| return codecInfo.isVendor(); |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isSoftwareCodec(String codecName) { |
| for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_ALL.getCodecInfos()) { |
| if (codecName.equals(codecInfo.getName())) { |
| return codecInfo.isSoftwareOnly(); |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isHardwareAcceleratedCodec(String codecName) { |
| for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_ALL.getCodecInfos()) { |
| if (codecName.equals(codecInfo.getName())) { |
| return codecInfo.isHardwareAccelerated(); |
| } |
| } |
| return false; |
| } |
| |
| protected static String paramToString(Object[] param) { |
| StringBuilder paramStr = new StringBuilder("[ "); |
| for (int j = 0; j < param.length - 1; j++) { |
| Object o = param[j]; |
| if (o == null) { |
| paramStr.append("null, "); |
| } else if (o instanceof String[]) { |
| int length = Math.min(((String[]) o).length, 3); |
| paramStr.append("{"); |
| for (int i = 0; i < length; i++) { |
| paramStr.append(((String[]) o)[i]).append(", "); |
| } |
| paramStr.delete(paramStr.length() - 2, paramStr.length()) |
| .append(length == ((String[]) o).length ? "}, " : ", ... }, "); |
| } else if (o instanceof int[]) { |
| paramStr.append("{"); |
| for (int i = 0; i < ((int[]) o).length; i++) { |
| paramStr.append(((int[]) o)[i]).append(", "); |
| } |
| paramStr.delete(paramStr.length() - 2, paramStr.length()).append("}, "); |
| } else if (o instanceof Map) { |
| int length = 0; |
| paramStr.append("{ "); |
| Map map = (Map) o; |
| for (Object key : map.keySet()) { |
| paramStr.append(key).append(" = ").append(map.get(key)).append(", "); |
| length++; |
| if (length > 1) break; |
| } |
| paramStr.delete(paramStr.length() - 2, paramStr.length()) |
| .append(length == map.size() ? "}, " : ", ... }, "); |
| } else if (o instanceof EncoderConfigParams[]) { |
| int length = Math.min(((EncoderConfigParams[]) o).length, 3); |
| paramStr.append("{"); |
| for (int i = 0; i < ((EncoderConfigParams[]) o).length; i++) { |
| paramStr.append(((EncoderConfigParams[]) o)[i]).append(", "); |
| } |
| paramStr.delete(paramStr.length() - 2, paramStr.length()) |
| .append(length == ((EncoderConfigParams[]) o).length ? "}, " : ", ... }, "); |
| } else paramStr.append(o).append(", "); |
| } |
| paramStr.delete(paramStr.length() - 2, paramStr.length()).append(" ]"); |
| return paramStr.toString(); |
| } |
| |
| public static ArrayList<String> compileRequiredMimeList(boolean isEncoder, boolean needAudio, |
| boolean needVideo) { |
| Set<String> list = new HashSet<>(); |
| if (!isEncoder) { |
| if (MediaUtils.hasAudioOutput() && needAudio) { |
| // sec 5.1.2 |
| list.add(MediaFormat.MIMETYPE_AUDIO_AAC); |
| list.add(MediaFormat.MIMETYPE_AUDIO_FLAC); |
| list.add(MediaFormat.MIMETYPE_AUDIO_MPEG); |
| list.add(MediaFormat.MIMETYPE_AUDIO_VORBIS); |
| list.add(MediaFormat.MIMETYPE_AUDIO_RAW); |
| list.add(MediaFormat.MIMETYPE_AUDIO_OPUS); |
| } |
| if (MediaUtils.isHandheld() || MediaUtils.isTablet() || MediaUtils.isTv() |
| || MediaUtils.isAutomotive()) { |
| // sec 2.2.2, 2.3.2, 2.5.2 |
| if (needAudio) { |
| list.add(MediaFormat.MIMETYPE_AUDIO_AAC); |
| } |
| if (needVideo) { |
| list.add(MediaFormat.MIMETYPE_VIDEO_AVC); |
| list.add(MediaFormat.MIMETYPE_VIDEO_MPEG4); |
| list.add(MediaFormat.MIMETYPE_VIDEO_H263); |
| list.add(MediaFormat.MIMETYPE_VIDEO_VP8); |
| list.add(MediaFormat.MIMETYPE_VIDEO_VP9); |
| } |
| } |
| if (MediaUtils.isHandheld() || MediaUtils.isTablet()) { |
| // sec 2.2.2 |
| if (needAudio) { |
| list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB); |
| list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB); |
| } |
| if (needVideo) { |
| list.add(MediaFormat.MIMETYPE_VIDEO_HEVC); |
| if (IS_AT_LEAST_U) { |
| list.add(MediaFormat.MIMETYPE_VIDEO_AV1); |
| } |
| } |
| } |
| if (MediaUtils.isTv() && needVideo) { |
| // sec 2.3.2 |
| list.add(MediaFormat.MIMETYPE_VIDEO_HEVC); |
| list.add(MediaFormat.MIMETYPE_VIDEO_MPEG2); |
| } |
| } else { |
| if (MediaUtils.hasMicrophone() && needAudio) { |
| // sec 5.1.1 |
| // TODO(b/154423550) |
| // list.add(MediaFormat.MIMETYPE_AUDIO_RAW); |
| list.add(MediaFormat.MIMETYPE_AUDIO_FLAC); |
| list.add(MediaFormat.MIMETYPE_AUDIO_OPUS); |
| } |
| if (MediaUtils.isHandheld() || MediaUtils.isTablet() || MediaUtils.isTv() |
| || MediaUtils.isAutomotive()) { |
| // sec 2.2.2, 2.3.2, 2.5.2 |
| if (needAudio) { |
| list.add(MediaFormat.MIMETYPE_AUDIO_AAC); |
| } |
| if (needVideo) { |
| list.add(MediaFormat.MIMETYPE_VIDEO_AVC); |
| list.add(MediaFormat.MIMETYPE_VIDEO_VP8); |
| } |
| } |
| if ((MediaUtils.isHandheld() || MediaUtils.isTablet()) && needAudio) { |
| // sec 2.2.2 |
| list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB); |
| list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB); |
| } |
| } |
| return new ArrayList<>(list); |
| } |
| |
| private static ArrayList<String> compileCompleteTestMimeList(boolean isEncoder, |
| boolean needAudio, boolean needVideo) { |
| ArrayList<String> mimes = new ArrayList<>(); |
| if (mimeSelKeys == null) { |
| ArrayList<String> cddRequiredMimeList = |
| compileRequiredMimeList(isEncoder, needAudio, needVideo); |
| MediaCodecInfo[] codecInfos = MEDIA_CODEC_LIST_REGULAR.getCodecInfos(); |
| for (MediaCodecInfo codecInfo : codecInfos) { |
| if (codecInfo.isEncoder() != isEncoder) continue; |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue; |
| String[] types = codecInfo.getSupportedTypes(); |
| for (String type : types) { |
| if (mediaTypePrefix != null && !type.startsWith(mediaTypePrefix)) { |
| continue; |
| } |
| if (!needAudio && type.startsWith("audio/")) continue; |
| if (!needVideo && type.startsWith("video/")) continue; |
| if (!mimes.contains(type)) { |
| mimes.add(type); |
| } |
| } |
| } |
| if (mediaTypePrefix != null) { |
| return mimes; |
| } |
| // feature_video_output is not exposed to package manager. Testing for video output |
| // ports, such as VGA, HDMI, DisplayPort, or a wireless port for display is also not |
| // direct. |
| /* sec 5.2: device implementations include an embedded screen display with the |
| diagonal length of at least 2.5 inches or include a video output port or declare the |
| support of a camera */ |
| if (isEncoder && needVideo |
| && (MediaUtils.hasCamera() || MediaUtils.getScreenSizeInInches() >= 2.5) |
| && !mimes.contains(MediaFormat.MIMETYPE_VIDEO_AVC) |
| && !mimes.contains(MediaFormat.MIMETYPE_VIDEO_VP8)) { |
| // Add required cdd mimes here so that respective codec tests fail. |
| mimes.add(MediaFormat.MIMETYPE_VIDEO_AVC); |
| mimes.add(MediaFormat.MIMETYPE_VIDEO_VP8); |
| Log.e(LOG_TAG, "device must support at least one of VP8 or AVC video encoders"); |
| } |
| for (String mime : cddRequiredMimeList) { |
| if (!mimes.contains(mime)) { |
| // Add required cdd mimes here so that respective codec tests fail. |
| mimes.add(mime); |
| Log.e(LOG_TAG, "no codec found for mime " + mime + " as required by cdd"); |
| } |
| } |
| } else { |
| for (Map.Entry<String, String> entry : CODEC_SEL_KEY_MIME_MAP.entrySet()) { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| if (mimeSelKeys.contains(key) && !mimes.contains(value)) mimes.add(value); |
| } |
| } |
| return mimes; |
| } |
| |
| public static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList, |
| boolean isEncoder, boolean needAudio, boolean needVideo, boolean mustTestAllCodecs) { |
| return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, |
| mustTestAllCodecs, ComponentClass.ALL); |
| } |
| |
| public static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList, |
| boolean isEncoder, boolean needAudio, boolean needVideo, boolean mustTestAllCodecs, |
| ComponentClass selectSwitch) { |
| ArrayList<String> mimes = compileCompleteTestMimeList(isEncoder, needAudio, needVideo); |
| ArrayList<String> cddRequiredMimeList = |
| compileRequiredMimeList(isEncoder, needAudio, needVideo); |
| final List<Object[]> argsList = new ArrayList<>(); |
| int argLength = exhaustiveArgsList.get(0).length; |
| for (String mime : mimes) { |
| ArrayList<String> totalListOfCodecs = |
| selectCodecs(mime, null, null, isEncoder, selectSwitch); |
| ArrayList<String> listOfCodecs = new ArrayList<>(); |
| if (codecPrefix != null) { |
| for (String codec : totalListOfCodecs) { |
| if (codec.startsWith(codecPrefix)) { |
| listOfCodecs.add(codec); |
| } |
| } |
| } else { |
| listOfCodecs = totalListOfCodecs; |
| } |
| if (mustTestAllCodecs && listOfCodecs.size() == 0 && codecPrefix == null) { |
| listOfCodecs.add(INVALID_CODEC + mime); |
| } |
| boolean miss = true; |
| for (Object[] arg : exhaustiveArgsList) { |
| if (mime.equals(arg[0])) { |
| for (String codec : listOfCodecs) { |
| Object[] argUpdate = new Object[argLength + 2]; |
| argUpdate[0] = codec; |
| System.arraycopy(arg, 0, argUpdate, 1, argLength); |
| argUpdate[argLength + 1] = paramToString(argUpdate); |
| argsList.add(argUpdate); |
| } |
| miss = false; |
| } |
| } |
| if (miss && mustTestAllCodecs) { |
| if (!cddRequiredMimeList.contains(mime)) { |
| Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime); |
| continue; |
| } |
| for (String codec : listOfCodecs) { |
| Object[] argUpdate = new Object[argLength + 2]; |
| argUpdate[0] = codec; |
| argUpdate[1] = mime; |
| System.arraycopy(exhaustiveArgsList.get(0), 1, argUpdate, 2, argLength - 1); |
| argUpdate[argLength + 1] = paramToString(argUpdate); |
| argsList.add(argUpdate); |
| } |
| } |
| } |
| return argsList; |
| } |
| |
| public static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats, |
| String[] features, boolean isEncoder) { |
| return selectCodecs(mime, formats, features, isEncoder, ComponentClass.ALL); |
| } |
| |
| public static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats, |
| String[] features, boolean isEncoder, ComponentClass selectSwitch) { |
| MediaCodecInfo[] codecInfos = MEDIA_CODEC_LIST_REGULAR.getCodecInfos(); |
| ArrayList<String> listOfCodecs = new ArrayList<>(); |
| for (MediaCodecInfo codecInfo : codecInfos) { |
| if (codecInfo.isEncoder() != isEncoder) continue; |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue; |
| if (selectSwitch == ComponentClass.HARDWARE && !codecInfo.isHardwareAccelerated()) { |
| continue; |
| } else if (selectSwitch == ComponentClass.SOFTWARE && !codecInfo.isSoftwareOnly()) { |
| continue; |
| } |
| String[] types = codecInfo.getSupportedTypes(); |
| for (String type : types) { |
| if (type.equalsIgnoreCase(mime)) { |
| boolean isOk = true; |
| MediaCodecInfo.CodecCapabilities codecCapabilities = |
| codecInfo.getCapabilitiesForType(type); |
| if (formats != null) { |
| for (MediaFormat format : formats) { |
| if (!codecCapabilities.isFormatSupported(format)) { |
| isOk = false; |
| break; |
| } |
| } |
| } |
| if (features != null) { |
| for (String feature : features) { |
| if (!codecCapabilities.isFeatureSupported(feature)) { |
| isOk = false; |
| break; |
| } |
| } |
| } |
| if (isOk) listOfCodecs.add(codecInfo.getName()); |
| } |
| } |
| } |
| return listOfCodecs; |
| } |
| |
| public static int getWidth(MediaFormat format) { |
| int width = format.getInteger(MediaFormat.KEY_WIDTH, -1); |
| if (format.containsKey("crop-left") && format.containsKey("crop-right")) { |
| width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left"); |
| } |
| return width; |
| } |
| |
| public static int getHeight(MediaFormat format) { |
| int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1); |
| if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) { |
| height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top"); |
| } |
| return height; |
| } |
| |
| public static byte[] loadByteArrayFromString(final String str) { |
| if (str == null) { |
| return null; |
| } |
| Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}"); |
| Matcher matcher = pattern.matcher(str); |
| // allocate a large enough byte array first |
| byte[] tempArray = new byte[str.length() / 2]; |
| int i = 0; |
| while (matcher.find()) { |
| tempArray[i++] = (byte) Integer.parseInt(matcher.group(), 16); |
| } |
| return Arrays.copyOfRange(tempArray, 0, i); |
| } |
| |
| protected abstract void enqueueInput(int bufferIndex) throws IOException; |
| |
| protected abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info); |
| |
| @Rule |
| public final TestName mTestName = new TestName(); |
| |
| @Before |
| public void setUpCodecTestBase() { |
| mTestConfig.setLength(0); |
| mTestConfig.append("\n################## Test Details ####################\n"); |
| mTestConfig.append("Test Name :- ").append(mTestName.getMethodName()).append("\n"); |
| mTestConfig.append("Test Parameters :- ").append(mAllTestParams).append("\n"); |
| if (mCodecName != null && mCodecName.startsWith(INVALID_CODEC)) { |
| fail("no valid component available for current test \n" + mTestConfig); |
| } |
| } |
| |
| @After |
| public void tearDownCodecTestBase() { |
| mSurface = null; |
| if (mActivity != null) { |
| mActivity.finish(); |
| mActivity = null; |
| } |
| if (mCodec != null) { |
| mCodec.release(); |
| mCodec = null; |
| } |
| } |
| |
| protected void configureCodec(MediaFormat format, boolean isAsync, |
| boolean signalEOSWithLastFrame, boolean isEncoder) { |
| resetContext(isAsync, signalEOSWithLastFrame); |
| mAsyncHandle.setCallBack(mCodec, isAsync); |
| // signalEOS flag has nothing to do with configure. We are using this flag to try all |
| // available configure apis |
| if (signalEOSWithLastFrame) { |
| mCodec.configure(format, mSurface, null, |
| isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0); |
| } else { |
| mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, |
| null); |
| } |
| mTestEnv.setLength(0); |
| mTestEnv.append("################### Test Environment #####################\n"); |
| mTestEnv.append(String.format("Component under test :- %s \n", mCodecName)); |
| mTestEnv.append("Format under test :- ").append(format).append("\n"); |
| mTestEnv.append(String.format("Component operating in :- %s mode \n", |
| (isAsync ? "asynchronous" : "synchronous"))); |
| mTestEnv.append(String.format("Component received input eos :- %s \n", |
| (signalEOSWithLastFrame ? "with full buffer" : "with empty buffer"))); |
| if (ENABLE_LOGS) { |
| Log.v(LOG_TAG, "codec configured"); |
| } |
| } |
| |
| public OutputManager getOutputManager() { |
| return mOutputBuff; |
| } |
| |
| public MediaFormat getOutputFormat() { |
| return mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() : mOutFormat; |
| } |
| |
| protected void flushCodec() { |
| mCodec.flush(); |
| // TODO(b/147576107): is it ok to clearQueues right away or wait for some signal |
| mAsyncHandle.clearQueues(); |
| mSawInputEOS = false; |
| mSawOutputEOS = false; |
| mInputCount = 0; |
| mOutputCount = 0; |
| mPrevOutputPts = Long.MIN_VALUE; |
| if (ENABLE_LOGS) { |
| Log.v(LOG_TAG, "codec flushed"); |
| } |
| } |
| |
| protected void reConfigureCodec(MediaFormat format, boolean isAsync, |
| boolean signalEOSWithLastFrame, boolean isEncoder) { |
| /* TODO(b/147348711) */ |
| if (false) mCodec.stop(); |
| else mCodec.reset(); |
| configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder); |
| } |
| |
| protected void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { |
| mAsyncHandle.resetContext(); |
| mIsCodecInAsyncMode = isAsync; |
| mSawInputEOS = false; |
| mSawOutputEOS = false; |
| mSignalEOSWithLastFrame = signalEOSWithLastFrame; |
| mInputCount = 0; |
| mOutputCount = 0; |
| mPrevOutputPts = Long.MIN_VALUE; |
| mSignalledOutFormatChanged = false; |
| } |
| |
| protected void enqueueEOS(int bufferIndex) { |
| if (!mSawInputEOS) { |
| mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); |
| mSawInputEOS = true; |
| if (ENABLE_LOGS) { |
| Log.v(LOG_TAG, "Queued End of Stream"); |
| } |
| } |
| } |
| |
| protected void doWork(int frameLimit) throws InterruptedException, IOException { |
| int frameCount = 0; |
| if (mIsCodecInAsyncMode) { |
| // dequeue output after inputEOS is expected to be done in waitForAllOutputs() |
| while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) { |
| Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); |
| if (element != null) { |
| int bufferID = element.first; |
| MediaCodec.BufferInfo info = element.second; |
| if (info != null) { |
| // <id, info> corresponds to output callback. Handle it accordingly |
| dequeueOutput(bufferID, info); |
| } else { |
| // <id, null> corresponds to input callback. Handle it accordingly |
| enqueueInput(bufferID); |
| frameCount++; |
| } |
| } |
| } |
| } else { |
| MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); |
| // dequeue output after inputEOS is expected to be done in waitForAllOutputs() |
| while (!mSawInputEOS && frameCount < frameLimit) { |
| int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); |
| if (outputBufferId >= 0) { |
| dequeueOutput(outputBufferId, outInfo); |
| } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
| mOutFormat = mCodec.getOutputFormat(); |
| mSignalledOutFormatChanged = true; |
| } |
| int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); |
| if (inputBufferId != -1) { |
| enqueueInput(inputBufferId); |
| frameCount++; |
| } |
| } |
| } |
| } |
| |
| protected void queueEOS() throws InterruptedException { |
| if (mIsCodecInAsyncMode) { |
| while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) { |
| Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork(); |
| if (element != null) { |
| int bufferID = element.first; |
| MediaCodec.BufferInfo info = element.second; |
| if (info != null) { |
| dequeueOutput(bufferID, info); |
| } else { |
| enqueueEOS(element.first); |
| } |
| } |
| } |
| } else { |
| MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); |
| while (!mSawInputEOS) { |
| int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); |
| if (outputBufferId >= 0) { |
| dequeueOutput(outputBufferId, outInfo); |
| } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
| mOutFormat = mCodec.getOutputFormat(); |
| mSignalledOutFormatChanged = true; |
| } |
| int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); |
| if (inputBufferId != -1) { |
| enqueueEOS(inputBufferId); |
| } |
| } |
| } |
| } |
| |
| protected void waitForAllOutputs() throws InterruptedException { |
| if (mIsCodecInAsyncMode) { |
| while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) { |
| Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput(); |
| if (element != null) { |
| dequeueOutput(element.first, element.second); |
| } |
| } |
| } else { |
| MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); |
| while (!mSawOutputEOS) { |
| int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); |
| if (outputBufferId >= 0) { |
| dequeueOutput(outputBufferId, outInfo); |
| } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
| mOutFormat = mCodec.getOutputFormat(); |
| mSignalledOutFormatChanged = true; |
| } |
| } |
| } |
| validateTestState(); |
| } |
| |
| void validateTestState() { |
| assertFalse("Encountered error in async mode. \n" + mTestConfig + mTestEnv |
| + mAsyncHandle.getErrMsg(), mAsyncHandle.hasSeenError()); |
| if (mInputCount > 0) { |
| assertTrue(String.format("fed %d input frames, received no output frames \n", |
| mInputCount) + mTestConfig + mTestEnv, mOutputCount > 0); |
| } |
| /*if (mInputCount == 0 && mInputCount != mOutputCount) { |
| String msg = String.format("The number of output frames received is not same as number " |
| + "of input frames queued. Output count is %d, Input count is %d \n", |
| mOutputCount, mInputCount); |
| // check the pts lists to see what frames are dropped, the below call is needed to |
| // get useful error messages |
| boolean unused = mOutputBuff.isOutPtsListIdenticalToInpPtsList(true); |
| fail(msg + mTestConfig + mTestEnv + mOutputBuff.getErrMsg()); |
| }*/ |
| } |
| |
| protected void insertHdrDynamicInfo(byte[] info) { |
| final Bundle params = new Bundle(); |
| params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info); |
| mCodec.setParameters(params); |
| } |
| |
| public boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) { |
| if (inpFormat == null || outFormat == null) return false; |
| String inpMime = inpFormat.getString(MediaFormat.KEY_MIME); |
| String outMime = outFormat.getString(MediaFormat.KEY_MIME); |
| // not comparing input and output mimes because for a codec, mime is raw on one side and |
| // encoded type on the other |
| if (outMime.startsWith("audio/")) { |
| return (inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1) |
| == outFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -2)) |
| && (inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1) |
| == outFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -2)) |
| && inpMime.startsWith("audio/"); |
| } else if (outMime.startsWith("video/")) { |
| return getWidth(inpFormat) == getWidth(outFormat) |
| && getHeight(inpFormat) == getHeight(outFormat) && inpMime.startsWith("video/"); |
| } |
| return true; |
| } |
| |
| protected PersistableBundle validateMetrics(String codec) { |
| PersistableBundle metrics = mCodec.getMetrics(); |
| assertNotNull("error! MediaCodec.getMetrics() returns null \n" + mTestConfig + mTestEnv, |
| metrics); |
| assertEquals("error! metrics#MetricsConstants.CODEC is not as expected \n" + mTestConfig |
| + mTestEnv, metrics.getString(MediaCodec.MetricsConstants.CODEC), codec); |
| assertEquals("error! metrics#MetricsConstants.MODE is not as expected \n" + mTestConfig |
| + mTestEnv, mIsAudio ? MediaCodec.MetricsConstants.MODE_AUDIO : |
| MediaCodec.MetricsConstants.MODE_VIDEO, |
| metrics.getString(MediaCodec.MetricsConstants.MODE)); |
| return metrics; |
| } |
| |
| protected PersistableBundle validateMetrics(String codec, MediaFormat format) { |
| PersistableBundle metrics = validateMetrics(codec); |
| if (mIsVideo) { |
| assertEquals("error! metrics#MetricsConstants.WIDTH is not as expected\n" + mTestConfig |
| + mTestEnv, metrics.getInt(MediaCodec.MetricsConstants.WIDTH), |
| getWidth(format)); |
| assertEquals("error! metrics#MetricsConstants.HEIGHT is not as expected\n" + mTestConfig |
| + mTestEnv, metrics.getInt(MediaCodec.MetricsConstants.HEIGHT), |
| getHeight(format)); |
| } |
| assertEquals("error! metrics#MetricsConstants.SECURE is not as expected\n" + mTestConfig |
| + mTestEnv, 0, metrics.getInt(MediaCodec.MetricsConstants.SECURE)); |
| return metrics; |
| } |
| |
| public void validateColorAspects(MediaFormat fmt, int range, int standard, int transfer) { |
| int colorRange = fmt.getInteger(MediaFormat.KEY_COLOR_RANGE, UNSPECIFIED); |
| int colorStandard = fmt.getInteger(MediaFormat.KEY_COLOR_STANDARD, UNSPECIFIED); |
| int colorTransfer = fmt.getInteger(MediaFormat.KEY_COLOR_TRANSFER, UNSPECIFIED); |
| if (range > UNSPECIFIED) { |
| assertEquals("error! color range mismatch \n" + mTestConfig + mTestEnv, range, |
| colorRange); |
| } |
| if (standard > UNSPECIFIED) { |
| assertEquals("error! color standard mismatch \n" + mTestConfig + mTestEnv, standard, |
| colorStandard); |
| } |
| if (transfer > UNSPECIFIED) { |
| assertEquals("error! color transfer mismatch \n" + mTestConfig + mTestEnv, transfer, |
| colorTransfer); |
| } |
| } |
| |
| protected void validateHDRInfo(MediaFormat fmt, String hdrInfoKey, ByteBuffer hdrInfoRef) { |
| ByteBuffer hdrInfo = fmt.getByteBuffer(hdrInfoKey, null); |
| assertNotNull("error! no " + hdrInfoKey + " present in format : " + fmt + "\n " |
| + mTestConfig + mTestEnv, hdrInfo); |
| if (!hdrInfoRef.equals(hdrInfo)) { |
| StringBuilder msg = new StringBuilder( |
| "################### Error Details #####################\n"); |
| byte[] ref = new byte[hdrInfoRef.capacity()]; |
| hdrInfoRef.get(ref); |
| hdrInfoRef.rewind(); |
| byte[] test = new byte[hdrInfo.capacity()]; |
| hdrInfo.get(test); |
| hdrInfo.rewind(); |
| msg.append("ref info :- \n"); |
| for (byte b : ref) { |
| msg.append(String.format("%2x ", b)); |
| } |
| msg.append("\ntest info :- \n"); |
| for (byte b : test) { |
| msg.append(String.format("%2x ", b)); |
| } |
| fail("error! mismatch seen between ref and test info of " + hdrInfoKey + "\n" |
| + mTestConfig + mTestEnv + msg); |
| } |
| } |
| |
| protected void setUpSurface(CodecTestActivity activity) throws InterruptedException { |
| activity.waitTillSurfaceIsCreated(); |
| mSurface = activity.getSurface(); |
| assertNotNull("Surface created is null \n" + mTestConfig + mTestEnv, mSurface); |
| assertTrue("Surface created is invalid \n" + mTestConfig + mTestEnv, mSurface.isValid()); |
| } |
| } |