1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.video.cts;
18 
19 import android.media.MediaCodec;
20 import android.media.MediaCodecInfo;
21 import android.media.MediaCodecList;
22 import android.media.MediaFormat;
23 
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Map;
28 
29 import static org.junit.Assert.assertNotNull;
30 import static org.junit.Assert.assertTrue;
31 import static org.junit.Assume.assumeTrue;
32 
33 class CodecEncoderPerformanceTestBase extends CodecPerformanceTestBase {
34     private static final String LOG_TAG = CodecEncoderPerformanceTest.class.getSimpleName();
35     private static final Map<String, Float> transcodeAVCToTargetBitrateMap = new HashMap<>();
36 
37     final String mEncoderMime;
38     final String mEncoderName;
39     final int mBitrate;
40     double mAchievedFps;
41 
42     private boolean mSawEncInputEOS = false;
43     private boolean mSawEncOutputEOS = false;
44     private int mEncOutputNum = 0;
45     private MediaCodec mEncoder;
46     private MediaFormat mEncoderFormat;
47 
48     // Suggested bitrate scaling factors for transcoding avc to target format.
49     static {
transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, 1.25f)50         transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, 1.25f);
transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_AVC, 1.0f)51         transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_AVC, 1.0f);
transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, 0.7f)52         transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, 0.7f);
transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, 0.6f)53         transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, 0.6f);
transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, 0.4f)54         transcodeAVCToTargetBitrateMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, 0.4f);
55     }
56 
getBitrateScalingFactor(String mime)57     public static float getBitrateScalingFactor(String mime) {
58         return transcodeAVCToTargetBitrateMap.getOrDefault(mime, 1.5f);
59     }
60 
CodecEncoderPerformanceTestBase(String decoderName, String testFile, String encoderMime, String encoderName, int bitrate, int keyPriority, float scalingFactor)61     public CodecEncoderPerformanceTestBase(String decoderName, String testFile, String encoderMime,
62             String encoderName, int bitrate, int keyPriority, float scalingFactor) {
63         super(decoderName, testFile, keyPriority, scalingFactor);
64         mEncoderMime = encoderMime;
65         mEncoderName = encoderName;
66         mBitrate = bitrate;
67     }
68 
getMimesOfAvailableHardwareVideoEncoders()69     static ArrayList<String> getMimesOfAvailableHardwareVideoEncoders() {
70         MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
71         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
72         ArrayList<String> listOfMimes = new ArrayList<>();
73         for (MediaCodecInfo codecInfo : codecInfos) {
74             if (!codecInfo.isEncoder() || !codecInfo.isHardwareAccelerated()) continue;
75             String[] types = codecInfo.getSupportedTypes();
76             for (String type : types) {
77                 if (type.startsWith("video/") && !listOfMimes.contains(type)) {
78                     listOfMimes.add(type);
79                 }
80             }
81         }
82         return listOfMimes;
83     }
84 
setUpEncoderFormat(MediaFormat format, String mime, int bitrate)85     public static MediaFormat setUpEncoderFormat(MediaFormat format, String mime, int bitrate) {
86         MediaFormat fmt = new MediaFormat();
87         fmt.setString(MediaFormat.KEY_MIME, mime);
88         fmt.setInteger(MediaFormat.KEY_WIDTH, format.getInteger(MediaFormat.KEY_WIDTH));
89         fmt.setInteger(MediaFormat.KEY_HEIGHT, format.getInteger(MediaFormat.KEY_HEIGHT));
90         fmt.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
91         fmt.setInteger(MediaFormat.KEY_FRAME_RATE,
92                 format.getInteger(MediaFormat.KEY_FRAME_RATE, 30));
93         fmt.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
94         fmt.setInteger(MediaFormat.KEY_COLOR_FORMAT,
95                 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
96         return fmt;
97     }
98 
dequeueEncoderOutput(int bufferIndex, MediaCodec.BufferInfo info)99     private void dequeueEncoderOutput(int bufferIndex, MediaCodec.BufferInfo info) {
100         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
101             mEncOutputNum++;
102         }
103         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
104             mSawEncOutputEOS = true;
105         }
106         mEncoder.releaseOutputBuffer(bufferIndex, false);
107     }
108 
setUpFormats(MediaFormat format)109     private void setUpFormats(MediaFormat format) throws IOException {
110         mDecoderFormat = new MediaFormat(format);
111         mDecoderFormat.setInteger(MediaFormat.KEY_PRIORITY, mKeyPriority);
112         mEncoderFormat = setUpEncoderFormat(mDecoderFormat, mEncoderMime, mBitrate);
113         mEncoderFormat.setInteger(MediaFormat.KEY_PRIORITY, mKeyPriority);
114         double maxOperatingRateDecoder = getMaxOperatingRate(mDecoderName, mDecoderMime);
115         double maxOperatingRateEncoder = getMaxOperatingRate(mEncoderName, mEncoderMime);
116         mOperatingRateExpected = Math.min(maxOperatingRateDecoder, maxOperatingRateEncoder);
117         // As both decoder and encoder are running in concurrently, expected rate is halved
118         mOperatingRateExpected /= 2.0;
119         if (mMaxOpRateScalingFactor > 0.0f) {
120             int operatingRateToSet = (int) (mOperatingRateExpected * mMaxOpRateScalingFactor);
121             if (mMaxOpRateScalingFactor < 1.0f) {
122                 mOperatingRateExpected = operatingRateToSet;
123             }
124             mDecoderFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, operatingRateToSet);
125             mEncoderFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, operatingRateToSet);
126         } else if (mMaxOpRateScalingFactor < 0.0f) {
127             mDecoderFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, -1);
128             mEncoderFormat.setInteger(MediaFormat.KEY_OPERATING_RATE, -1);
129         }
130         mEncoderFormat.setInteger(MediaFormat.KEY_COMPLEXITY,
131                 getEncoderMinComplexity(mEncoderName, mEncoderMime));
132     }
133 
doWork()134     private void doWork() {
135         while (!mSawEncOutputEOS) {
136             if (!mSawDecInputEOS) {
137                 int inputBufIndex = mDecoder.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
138                 if (inputBufIndex >= 0) {
139                     enqueueDecoderInput(inputBufIndex);
140                 }
141             }
142             if (!mSawDecOutputEOS) {
143                 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
144                 int outputBufIndex = mDecoder.dequeueOutputBuffer(info, Q_DEQ_TIMEOUT_US);
145                 if (outputBufIndex >= 0) {
146                     dequeueDecoderOutput(outputBufIndex, info, true);
147                 }
148             }
149             if (mSawDecOutputEOS && !mSawEncInputEOS) {
150                 mEncoder.signalEndOfInputStream();
151                 mSawEncInputEOS = true;
152             }
153             MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
154             int outputBufferId = mEncoder.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
155             if (outputBufferId >= 0) {
156                 dequeueEncoderOutput(outputBufferId, outInfo);
157             }
158         }
159     }
160 
encode()161     public void encode() throws IOException {
162         MediaFormat format = setUpDecoderInput();
163         assertNotNull("Video track not present in " + mTestFile, format);
164 
165         if (EXCLUDE_ENCODER_MAX_RESOLUTION) {
166             int maxFrameSize = getMaxFrameSize(mEncoderName, mEncoderMime);
167             assumeTrue(mWidth + "x" + mHeight + " is skipped as it not less than half of " +
168                     "maximum frame size: " + maxFrameSize + " supported by the encoder.",
169                     mWidth * mHeight < maxFrameSize / 2);
170         }
171 
172         setUpFormats(format);
173         mDecoder = MediaCodec.createByCodecName(mDecoderName);
174         mEncoder = MediaCodec.createByCodecName(mEncoderName);
175         mEncoder.configure(mEncoderFormat, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
176         mSurface = mEncoder.createInputSurface();
177         assertTrue("Surface created is null.", mSurface != null);
178         assertTrue("Surface is not valid", mSurface.isValid());
179         mDecoder.configure(mDecoderFormat, mSurface, null, 0);
180         mDecoder.start();
181         mEncoder.start();
182         long start = System.currentTimeMillis();
183         doWork();
184         long finish = System.currentTimeMillis();
185         mEncoder.stop();
186         mSurface.release();
187         mEncoder.release();
188         mDecoder.stop();
189         mDecoder.release();
190         mEncoder = null;
191         mDecoder = null;
192         assertTrue("Encoder output count is zero", mEncOutputNum > 0);
193         mAchievedFps = mEncOutputNum / ((finish - start) / 1000.0);
194     }
195 
196     @Override
finalize()197     protected void finalize() throws Throwable {
198         if (mDecoder != null) {
199             mDecoder.release();
200             mDecoder = null;
201         }
202         if (mEncoder != null) {
203             mEncoder.release();
204             mEncoder = null;
205         }
206     }
207 }
208