| /* |
| * Copyright (C) 2021 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.mediapc.cts; |
| |
| import static android.mediapc.cts.CodecTestBase.SELECT_ALL; |
| import static android.mediapc.cts.CodecTestBase.SELECT_AUDIO; |
| import static android.mediapc.cts.CodecTestBase.SELECT_HARDWARE; |
| import static android.mediapc.cts.CodecTestBase.SELECT_VIDEO; |
| import static android.mediapc.cts.CodecTestBase.getMimesOfAvailableCodecs; |
| import static android.mediapc.cts.CodecTestBase.selectCodecs; |
| import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeFalse; |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.app.Instrumentation; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.media.MediaCodec; |
| import android.media.MediaCodecInfo; |
| import android.media.MediaFormat; |
| import android.media.MediaRecorder; |
| import android.mediapc.cts.common.PerformanceClassEvaluator; |
| import android.mediapc.cts.common.Utils; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.view.Surface; |
| |
| import androidx.test.filters.LargeTest; |
| import androidx.test.platform.app.InstrumentationRegistry; |
| import androidx.test.rule.ActivityTestRule; |
| |
| import com.android.compatibility.common.util.CddTest; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestName; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * The following test class validates the codec initialization latency (time for codec create + |
| * configure) for the audio codecs and hardware video codecs available in the device, under the |
| * load condition (Transcode + MediaRecorder session Audio(Microphone) and 1080p Video(Camera)). |
| */ |
| @RunWith(Parameterized.class) |
| public class CodecInitializationLatencyTest { |
| private static final String LOG_TAG = CodecInitializationLatencyTest.class.getSimpleName(); |
| private static final boolean[] boolStates = {false, true}; |
| |
| private static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC; |
| private static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC; |
| private static final String AVC_TRANSCODE_FILE = "bbb_1280x720_3mbps_30fps_avc.mp4"; |
| private static String AVC_DECODER_NAME; |
| private static String AVC_ENCODER_NAME; |
| private static final Map<String, String> mTestFiles = new HashMap<>(); |
| |
| @Rule |
| public final TestName mTestName = new TestName(); |
| |
| static { |
| // TODO(b/222006626): Add tests vectors for remaining media types |
| // Audio media types |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_stereo_48kHz_128kbps_aac.mp4"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_AMR_NB, "bbb_mono_8kHz_12.2kbps_amrnb.3gp"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_AMR_WB, "bbb_1ch_16kHz_23kbps_amrwb.3gp"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_1ch_12kHz_lvl4_flac.mka"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_G711_ALAW, "bbb_2ch_8kHz_alaw.wav"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_G711_MLAW, "bbb_2ch_8kHz_mulaw.wav"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_8kHz_lame_cbr.mp3"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_MSGSM, "bbb_1ch_8kHz_gsm.wav"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_OPUS, "bbb_2ch_48kHz_opus.mka"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_8kHz.wav"); |
| mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_VORBIS, "bbb_stereo_48kHz_128kbps_vorbis.ogg"); |
| |
| // Video media types |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_H263, "bbb_cif_768kbps_30fps_h263.mp4"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_1920x1080_mpeg2_main_high.mp4"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_cif_768kbps_30fps_mpeg4.mkv"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm"); |
| mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm"); |
| } |
| |
| private final String mMime; |
| private final String mCodecName; |
| |
| private LoadStatus mTranscodeLoadStatus = null; |
| private Thread mTranscodeLoadThread = null; |
| private MediaRecorder mMediaRecorderLoad = null; |
| private File mTempRecordedFile = null; |
| private Surface mSurface = null; |
| private Exception mException = null; |
| |
| @Before |
| public void setUp() throws Exception { |
| Utils.assumeDeviceMeetsPerformanceClassPreconditions(); |
| |
| ArrayList<String> listOfAvcHwDecoders = selectHardwareCodecs(AVC, null, null, false); |
| assumeFalse("Test requires h/w avc decoder", listOfAvcHwDecoders.isEmpty()); |
| AVC_DECODER_NAME = listOfAvcHwDecoders.get(0); |
| |
| ArrayList<String> listOfAvcHwEncoders = selectHardwareCodecs(AVC, null, null, true); |
| assumeFalse("Test requires h/w avc encoder", listOfAvcHwEncoders.isEmpty()); |
| AVC_ENCODER_NAME = listOfAvcHwEncoders.get(0); |
| |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| Context context = instrumentation.getTargetContext(); |
| PackageManager packageManager = context.getPackageManager(); |
| assertNotNull(packageManager.getSystemAvailableFeatures()); |
| assumeTrue("The device doesn't have a camera", |
| packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)); |
| assumeTrue("The device doesn't have a microphone", |
| packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)); |
| createSurface(); |
| startLoad(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| stopLoad(); |
| releaseSurface(); |
| } |
| |
| public CodecInitializationLatencyTest(String mimeType, String codecName) { |
| mMime = mimeType; |
| mCodecName = codecName; |
| } |
| |
| @Rule |
| public ActivityTestRule<TestActivity> mActivityRule = |
| new ActivityTestRule<>(TestActivity.class); |
| |
| /** |
| * Returns the list of parameters with mimetype and their codecs(for audio - all codecs, |
| * video - hardware codecs). |
| * |
| * @return Collection of Parameters {0}_{1} -- MIME_CodecName |
| */ |
| @Parameterized.Parameters(name = "{index}({0}_{1})") |
| public static Collection<Object[]> inputParams() { |
| // Prepares the params list with the required Hardware video codecs and all available |
| // audio codecs present in the device. |
| final List<Object[]> argsList = new ArrayList<>(); |
| Set<String> mimeSet = getMimesOfAvailableCodecs(SELECT_VIDEO, SELECT_HARDWARE); |
| mimeSet.addAll(getMimesOfAvailableCodecs(SELECT_AUDIO, SELECT_ALL)); |
| for (String mime : mimeSet) { |
| ArrayList<String> listOfCodecs; |
| if (mime.startsWith("audio/")) { |
| listOfCodecs = selectCodecs(mime, null, null, true); |
| listOfCodecs.addAll(selectCodecs(mime, null, null, false)); |
| } else { |
| listOfCodecs = selectHardwareCodecs(mime, null, null, true); |
| listOfCodecs.addAll(selectHardwareCodecs(mime, null, null, false)); |
| } |
| for (String codec : listOfCodecs) { |
| argsList.add(new Object[]{mime, codec}); |
| } |
| } |
| return argsList; |
| } |
| |
| private MediaRecorder createMediaRecorderLoad(Surface surface) throws Exception { |
| MediaRecorder mediaRecorder = new MediaRecorder(InstrumentationRegistry.getInstrumentation() |
| .getContext()); |
| mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); |
| mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); |
| mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); |
| mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); |
| mediaRecorder.setVideoEncoder(mMime.equalsIgnoreCase(HEVC) ? |
| MediaRecorder.VideoEncoder.HEVC : MediaRecorder.VideoEncoder.H264); |
| mediaRecorder.setOutputFile(mTempRecordedFile); |
| mediaRecorder.setVideoSize(1920, 1080); |
| mediaRecorder.setOrientationHint(0); |
| mediaRecorder.setPreviewDisplay(surface); |
| mediaRecorder.prepare(); |
| return mediaRecorder; |
| } |
| |
| private void startLoad() throws Exception { |
| // TODO: b/183671436 |
| // Create Transcode load (AVC Decoder(720p) + AVC Encoder(720p)) |
| mTranscodeLoadStatus = new LoadStatus(); |
| mTranscodeLoadThread = new Thread(() -> { |
| try { |
| TranscodeLoad transcodeLoad = new TranscodeLoad(AVC, AVC_TRANSCODE_FILE, |
| AVC_DECODER_NAME, AVC_ENCODER_NAME, mTranscodeLoadStatus); |
| transcodeLoad.doTranscode(); |
| } catch (Exception e) { |
| mException = e; |
| } |
| }); |
| // Create MediaRecorder Session - Audio (Microphone) + 1080p Video (Camera) |
| // Create a temp file to dump the MediaRecorder output. Later it will be deleted. |
| mTempRecordedFile = new File(WorkDir.getMediaDirString() + "tempOut.mp4"); |
| mTempRecordedFile.createNewFile(); |
| mMediaRecorderLoad = createMediaRecorderLoad(mSurface); |
| // Start the Loads |
| mTranscodeLoadThread.start(); |
| mMediaRecorderLoad.start(); |
| } |
| |
| private void stopLoad() throws Exception { |
| if (mTranscodeLoadStatus != null) { |
| mTranscodeLoadStatus.setLoadFinished(); |
| mTranscodeLoadStatus = null; |
| } |
| if (mTranscodeLoadThread != null) { |
| mTranscodeLoadThread.join(); |
| mTranscodeLoadThread = null; |
| } |
| if (mMediaRecorderLoad != null) { |
| // Note that a RuntimeException is intentionally thrown to the application, if no valid |
| // audio/video data has been received when stop() is called. This happens if stop() is |
| // called immediately after start(). So sleep for 1000ms inorder to make sure some |
| // data has been received between start() and stop(). |
| Thread.sleep(1000); |
| mMediaRecorderLoad.stop(); |
| mMediaRecorderLoad.release(); |
| mMediaRecorderLoad = null; |
| if (mTempRecordedFile != null && mTempRecordedFile.exists()) { |
| mTempRecordedFile.delete(); |
| mTempRecordedFile = null; |
| } |
| } |
| if (mException != null) throw mException; |
| } |
| |
| private void createSurface() throws InterruptedException { |
| mActivityRule.getActivity().waitTillSurfaceIsCreated(); |
| mSurface = mActivityRule.getActivity().getSurface(); |
| assertNotNull("Surface created is null.", mSurface); |
| assertTrue("Surface created is invalid.", mSurface.isValid()); |
| mActivityRule.getActivity().setScreenParams(1920, 1080, true); |
| } |
| |
| private void releaseSurface() { |
| if (mSurface != null) { |
| mSurface.release(); |
| mSurface = null; |
| } |
| } |
| |
| /** |
| * This test validates the initialization latency (time for codec create + configure) for |
| * audio and hw video codecs. |
| * |
| * <p>Measurements are taken 5 * 2(sync/async) * [1 or 2] |
| * (surface/non-surface for video) times. This also logs the stats: min, max, avg of the codec |
| * initialization latencies. |
| */ |
| @LargeTest |
| @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS) |
| @CddTest(requirements = { |
| "2.2.7.1/5.1/H-1-7", |
| "2.2.7.1/5.1/H-1-8", |
| "2.2.7.1/5.1/H-1-12", |
| "2.2.7.1/5.1/H-1-13",}) |
| public void testInitializationLatency() throws Exception { |
| MediaCodec codec = MediaCodec.createByCodecName(mCodecName); |
| boolean isEncoder = codec.getCodecInfo().isEncoder(); |
| boolean isAudio = mMime.startsWith("audio/"); |
| codec.release(); |
| final int NUM_MEASUREMENTS = 5; |
| // Test gathers initialization latency for a number of iterations and |
| // percentile is a variable used to control how many of these iterations |
| // need to meet the pass criteria. For eg. if NUM_MEASUREMENTS = 5, audio, sync and Async |
| // modes which is a total of 10 iterations, this translates to index 7. |
| final int percentile = 70; |
| long sumOfCodecInitializationLatencyMs = 0; |
| int count = 0; |
| int numOfActualMeasurements = |
| NUM_MEASUREMENTS * boolStates.length * ((!isEncoder && !isAudio) ? 2 : 1); |
| long[] codecInitializationLatencyMs = new long[numOfActualMeasurements]; |
| for (int i = 0; i < NUM_MEASUREMENTS; i++) { |
| for (boolean isAsync : boolStates) { |
| long latency; |
| if (isEncoder) { |
| EncoderInitializationLatency encoderInitializationLatency = |
| new EncoderInitializationLatency(mMime, mCodecName, isAsync); |
| latency = encoderInitializationLatency.calculateInitLatency(); |
| codecInitializationLatencyMs[count] = latency; |
| sumOfCodecInitializationLatencyMs += latency; |
| count++; |
| } else { |
| String testFile = mTestFiles.get(mMime); |
| assumeTrue("Add test vector for media type: " + mMime, testFile != null); |
| if (isAudio) { |
| DecoderInitializationLatency decoderInitializationLatency = |
| new DecoderInitializationLatency(mMime, mCodecName, testFile, |
| isAsync, false); |
| latency = decoderInitializationLatency.calculateInitLatency(); |
| codecInitializationLatencyMs[count] = latency; |
| sumOfCodecInitializationLatencyMs += latency; |
| count++; |
| } else { |
| for (boolean surfaceMode : boolStates) { |
| DecoderInitializationLatency decoderInitializationLatency = |
| new DecoderInitializationLatency(mMime, mCodecName, |
| testFile, |
| isAsync, surfaceMode); |
| latency = decoderInitializationLatency.calculateInitLatency(); |
| codecInitializationLatencyMs[count] = latency; |
| sumOfCodecInitializationLatencyMs += latency; |
| count++; |
| } |
| } |
| } |
| } |
| } |
| Arrays.sort(codecInitializationLatencyMs); |
| |
| String statsLog = String.format("CodecInitialization latency for mime: %s, " + |
| "Codec: %s, in Ms :: ", mMime, mCodecName); |
| Log.i(LOG_TAG, "Min " + statsLog + codecInitializationLatencyMs[0]); |
| Log.i(LOG_TAG, "Max " + statsLog + codecInitializationLatencyMs[count - 1]); |
| Log.i(LOG_TAG, "Avg " + statsLog + (sumOfCodecInitializationLatencyMs / count)); |
| long initializationLatency = codecInitializationLatencyMs[percentile * count / 100]; |
| |
| PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName); |
| PerformanceClassEvaluator.CodecInitLatencyRequirement r5_1__H_1_Latency = |
| isEncoder ? isAudio ? pce.addR5_1__H_1_8() : pce.addR5_1__H_1_7() |
| : isAudio ? pce.addR5_1__H_1_13() : pce.addR5_1__H_1_12(); |
| |
| r5_1__H_1_Latency.setCodecInitLatencyMs(initializationLatency); |
| |
| pce.submitAndCheck(); |
| } |
| |
| /** |
| * The following class calculates the encoder initialization latency (time for codec create + |
| * configure). |
| * |
| * <p>And also logs the time taken by the encoder for: |
| * (create + configure + start), |
| * (create + configure + start + first frame to enqueue), |
| * (create + configure + start + first frame to dequeue). |
| */ |
| static class EncoderInitializationLatency extends CodecEncoderTestBase { |
| private static final String LOG_TAG = EncoderInitializationLatency.class.getSimpleName(); |
| |
| private final String mEncoderName; |
| private final boolean mIsAsync; |
| |
| EncoderInitializationLatency(String mime, String encoderName, boolean isAsync) { |
| super(mime); |
| mEncoderName = encoderName; |
| mIsAsync = isAsync; |
| mSampleRate = 8000; |
| mFrameRate = 60; |
| } |
| |
| private MediaFormat setUpFormat() throws IOException { |
| MediaFormat format = new MediaFormat(); |
| format.setString(MediaFormat.KEY_MIME, mMime); |
| if (mIsAudio) { |
| if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) { |
| format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 10000); |
| } else { |
| format.setInteger(MediaFormat.KEY_BIT_RATE, 128000); |
| } |
| format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate); |
| format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); |
| } else { |
| MediaCodec codec = MediaCodec.createByCodecName(mEncoderName); |
| MediaCodecInfo.CodecCapabilities codecCapabilities = |
| codec.getCodecInfo().getCapabilitiesForType(mMime); |
| if (codecCapabilities.getVideoCapabilities().isSizeSupported(1920, 1080)) { |
| format.setInteger(MediaFormat.KEY_WIDTH, 1920); |
| format.setInteger(MediaFormat.KEY_HEIGHT, 1080); |
| format.setInteger(MediaFormat.KEY_BIT_RATE, 8000000); |
| } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(1280, 720)) { |
| format.setInteger(MediaFormat.KEY_WIDTH, 1280); |
| format.setInteger(MediaFormat.KEY_HEIGHT, 720); |
| format.setInteger(MediaFormat.KEY_BIT_RATE, 5000000); |
| } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(640, 480)) { |
| format.setInteger(MediaFormat.KEY_WIDTH, 640); |
| format.setInteger(MediaFormat.KEY_HEIGHT, 480); |
| format.setInteger(MediaFormat.KEY_BIT_RATE, 2000000); |
| } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(352, 288)) { |
| format.setInteger(MediaFormat.KEY_WIDTH, 352); |
| format.setInteger(MediaFormat.KEY_HEIGHT, 288); |
| format.setInteger(MediaFormat.KEY_BIT_RATE, 512000); |
| } else { |
| format.setInteger(MediaFormat.KEY_WIDTH, 176); |
| format.setInteger(MediaFormat.KEY_HEIGHT, 144); |
| format.setInteger(MediaFormat.KEY_BIT_RATE, 128000); |
| } |
| codec.release(); |
| format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate); |
| format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f); |
| format.setInteger(MediaFormat.KEY_COLOR_FORMAT, |
| MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); |
| } |
| return format; |
| } |
| |
| public long calculateInitLatency() throws Exception { |
| MediaFormat format = setUpFormat(); |
| if (mIsAudio) { |
| mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); |
| mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); |
| } else { |
| mWidth = format.getInteger(MediaFormat.KEY_WIDTH); |
| mHeight = format.getInteger(MediaFormat.KEY_HEIGHT); |
| } |
| setUpSource(mInputFile); |
| MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); |
| long enqueueTimeStamp = 0; |
| long dequeueTimeStamp = 0; |
| long baseTimeStamp = System.nanoTime(); |
| mCodec = MediaCodec.createByCodecName(mEncoderName); |
| resetContext(mIsAsync, false); |
| mAsyncHandle.setCallBack(mCodec, mIsAsync); |
| mCodec.configure(format, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null); |
| long configureTimeStamp = System.nanoTime(); |
| mCodec.start(); |
| long startTimeStamp = System.nanoTime(); |
| if (mIsAsync) { |
| // We will keep on feeding the input to encoder until we see the first dequeued |
| // frame. |
| 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) { |
| dequeueTimeStamp = System.nanoTime(); |
| dequeueOutput(bufferID, info); |
| break; |
| } else { |
| if (enqueueTimeStamp == 0) { |
| enqueueTimeStamp = System.nanoTime(); |
| } |
| enqueueInput(bufferID); |
| } |
| } |
| } |
| } else { |
| while (!mSawOutputEOS) { |
| if (!mSawInputEOS) { |
| int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); |
| if (inputBufferId > 0) { |
| if (enqueueTimeStamp == 0) { |
| enqueueTimeStamp = System.nanoTime(); |
| } |
| enqueueInput(inputBufferId); |
| } |
| } |
| int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); |
| if (outputBufferId >= 0) { |
| dequeueTimeStamp = System.nanoTime(); |
| dequeueOutput(outputBufferId, outInfo); |
| break; |
| } |
| } |
| } |
| queueEOS(); |
| waitForAllOutputs(); |
| mCodec.stop(); |
| mCodec.release(); |
| Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName + |
| " Time for (create + configure) in ns: " + |
| (configureTimeStamp - baseTimeStamp)); |
| Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName + |
| " Time for (create + configure + start) in ns: " + |
| (startTimeStamp - baseTimeStamp)); |
| Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName + |
| " Time for (create + configure + start + first frame to enqueue) in ns: " + |
| (enqueueTimeStamp - baseTimeStamp)); |
| Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName + |
| " Time for (create + configure + start + first frame to dequeue) in ns: " + |
| (dequeueTimeStamp - baseTimeStamp)); |
| long timeToConfigureMs = (configureTimeStamp - baseTimeStamp) / 1000000; |
| return timeToConfigureMs; |
| } |
| } |
| |
| /** |
| * The following class calculates the decoder initialization latency (time for codec create + |
| * configure). |
| * And also logs the time taken by the decoder for: |
| * (create + configure + start), |
| * (create + configure + start + first frame to enqueue), |
| * (create + configure + start + first frame to dequeue). |
| */ |
| static class DecoderInitializationLatency extends CodecDecoderTestBase { |
| private static final String LOG_TAG = DecoderInitializationLatency.class.getSimpleName(); |
| |
| private final String mDecoderName; |
| private final boolean mIsAsync; |
| |
| DecoderInitializationLatency(String mediaType, String decoderName, String testFile, |
| boolean isAsync, boolean surfaceMode) { |
| super(mediaType, testFile); |
| mDecoderName = decoderName; |
| mIsAsync = isAsync; |
| mSurface = mIsAudio ? null : |
| surfaceMode ? MediaCodec.createPersistentInputSurface() : null; |
| } |
| |
| public long calculateInitLatency() throws Exception { |
| MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); |
| MediaFormat format = setUpSource(mTestFile); |
| long enqueueTimeStamp = 0; |
| long dequeueTimeStamp = 0; |
| long baseTimeStamp = System.nanoTime(); |
| mCodec = MediaCodec.createByCodecName(mDecoderName); |
| resetContext(mIsAsync, false); |
| mAsyncHandle.setCallBack(mCodec, mIsAsync); |
| mCodec.configure(format, mSurface, 0, null); |
| long configureTimeStamp = System.nanoTime(); |
| mCodec.start(); |
| long startTimeStamp = System.nanoTime(); |
| if (mIsAsync) { |
| // We will keep on feeding the input to decoder until we see the first dequeued |
| // frame. |
| 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) { |
| dequeueTimeStamp = System.nanoTime(); |
| dequeueOutput(bufferID, info); |
| break; |
| } else { |
| if (enqueueTimeStamp == 0) { |
| enqueueTimeStamp = System.nanoTime(); |
| } |
| enqueueInput(bufferID); |
| } |
| } |
| } |
| } else { |
| while (!mSawOutputEOS) { |
| if (!mSawInputEOS) { |
| int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US); |
| if (inputBufferId >= 0) { |
| if (enqueueTimeStamp == 0) { |
| enqueueTimeStamp = System.nanoTime(); |
| } |
| enqueueInput(inputBufferId); |
| } |
| } |
| int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US); |
| if (outputBufferId >= 0) { |
| dequeueTimeStamp = System.nanoTime(); |
| dequeueOutput(outputBufferId, outInfo); |
| break; |
| } |
| } |
| } |
| queueEOS(); |
| waitForAllOutputs(); |
| mCodec.stop(); |
| mCodec.release(); |
| if (mSurface != null) { |
| mSurface.release(); |
| } |
| Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName + |
| " Time for (create + configure) in ns: " + |
| (configureTimeStamp - baseTimeStamp)); |
| Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName + |
| " Time for (create + configure + start) in ns: " + |
| (startTimeStamp - baseTimeStamp)); |
| Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName + |
| " Time for (create + configure + start + first frame to enqueue) in ns: " + |
| (enqueueTimeStamp - baseTimeStamp)); |
| Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName + |
| " Time for (create + configure + start + first frame to dequeue) in ns: " + |
| (dequeueTimeStamp - baseTimeStamp)); |
| long timeToConfigureMs = (configureTimeStamp - baseTimeStamp) / 1000000; |
| return timeToConfigureMs; |
| } |
| } |
| } |