• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2023 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.media.misc.cts;
18 
19 import android.app.Activity;
20 import android.media.MediaCodec;
21 import android.media.MediaCodecInfo;
22 import android.media.MediaCodecInfo.CodecCapabilities;
23 import android.media.MediaCodecInfo.VideoCapabilities;
24 import android.media.MediaCodecList;
25 import android.media.MediaFormat;
26 import android.os.Bundle;
27 import android.util.Log;
28 
29 import java.io.IOException;
30 import java.util.Vector;
31 
32 public class ResourceManagerCodecActivity extends Activity {
33     private static final String TAG = "ResourceManagerCodecActivity";
34     private static final int MAX_INSTANCES = 32;
35     private static final int FRAME_RATE = 30;
36     private static final int IFRAME_INTERVAL = 10;
37     private boolean mHighResolution = false;
38     private volatile boolean mGotReclaimedException = false;
39     private int mWidth = 0;
40     private int mHeight = 0;
41     private int mBitrate = 0;
42     private String mMime = MediaFormat.MIMETYPE_VIDEO_AVC;
43     private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
44     private Thread mWorkerThread;
45 
46     @Override
onCreate(Bundle savedInstanceState)47     protected void onCreate(Bundle savedInstanceState) {
48         Log.d(TAG, "onCreate called.");
49         super.onCreate(savedInstanceState);
50         // Making this as a background Activity
51         // so that high priority Activities can reclaim codec from this.
52         moveTaskToBack(true);
53 
54         Bundle extras = getIntent().getExtras();
55         if (extras != null) {
56             mHighResolution = extras.getBoolean("high-resolution", mHighResolution);
57             mMime = extras.getString("mime", mMime);
58         }
59 
60         if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
61             // As we haven't reached the limit with MAX_INSTANCES,
62             // no need to wait for reclaim exception.
63             Log.d(TAG, "We may not get reclaim event");
64         }
65 
66         useCodecs();
67     }
68 
69     @Override
onDestroy()70     protected void onDestroy() {
71         Log.d(TAG, "onDestroy called.");
72         super.onDestroy();
73     }
74 
75     // MediaCodec callback
76     private class TestCodecCallback extends MediaCodec.Callback {
77         @Override
onInputBufferAvailable(MediaCodec codec, int index)78         public void onInputBufferAvailable(MediaCodec codec, int index) {
79         }
80 
81         @Override
onOutputBufferAvailable( MediaCodec codec, int index, MediaCodec.BufferInfo info)82         public void onOutputBufferAvailable(
83                 MediaCodec codec, int index, MediaCodec.BufferInfo info) {
84         }
85 
86         @Override
onError(MediaCodec codec, MediaCodec.CodecException e)87         public void onError(MediaCodec codec, MediaCodec.CodecException e) {
88             Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
89         }
90 
91         @Override
onOutputFormatChanged(MediaCodec codec, MediaFormat format)92         public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
93             Log.d(TAG, "onOutputFormatChanged " + codec.toString());
94         }
95     }
96 
97     private MediaCodec.Callback mCallback = new TestCodecCallback();
98 
99     // Get a HW Codec info for a given mime (mMime, which is either AVC or HEVC)
getCodecInfo(boolean lookForDecoder)100     private MediaCodecInfo getCodecInfo(boolean lookForDecoder) {
101         MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
102         for (MediaCodecInfo info : mcl.getCodecInfos()) {
103             if (info.isSoftwareOnly()) {
104                 // not testing the sw codecs for now as currently there are't
105                 // any limit on how many concurrent sw codecs can be created.
106                 // Allowing too many codecs may lead system into low memory
107                 // situation and lmkd will kill the test activity and eventually
108                 // failing the test case.
109                 continue;
110             }
111             boolean isEncoder = info.isEncoder();
112             if (lookForDecoder && isEncoder) {
113                 // Looking for a decoder, but found an encoder.
114                 // Skip it
115                 continue;
116             }
117             if (!lookForDecoder && !isEncoder) {
118                 // Looking for an encoder, but found a decoder.
119                 // Skip it
120                 continue;
121             }
122             CodecCapabilities caps;
123             try {
124                 caps = info.getCapabilitiesForType(mMime);
125             } catch (IllegalArgumentException e) {
126                 // mime is not supported
127                 continue;
128             }
129             return info;
130         }
131 
132         return null;
133     }
134 
createVideoFormat(MediaCodecInfo info)135     private MediaFormat createVideoFormat(MediaCodecInfo info) {
136         CodecCapabilities caps = info.getCapabilitiesForType(mMime);
137         VideoCapabilities vcaps = caps.getVideoCapabilities();
138 
139         if (mHighResolution) {
140             mWidth = vcaps.getSupportedWidths().getUpper();
141             mHeight = vcaps.getSupportedHeightsFor(mWidth).getUpper();
142             mBitrate = vcaps.getBitrateRange().getUpper();
143         } else {
144             mWidth = vcaps.getSupportedWidths().getLower();
145             mHeight = vcaps.getSupportedHeightsFor(mWidth).getLower();
146             mBitrate = vcaps.getBitrateRange().getLower();
147         }
148 
149         Log.d(TAG, "Mime: " + mMime + " Resolution: " + mWidth + "x" + mHeight
150                  + " Bitrate: " + mBitrate);
151         MediaFormat format = MediaFormat.createVideoFormat(mMime, mWidth, mHeight);
152         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
153         format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
154         format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
155         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
156 
157         return format;
158     }
159 
createVideoEncoderFormat(MediaCodecInfo info)160     private MediaFormat createVideoEncoderFormat(MediaCodecInfo info) {
161         MediaFormat format = createVideoFormat(info);
162         format.setInteger("profile", 1);
163         format.setInteger("level", 1);
164         format.setInteger("priority", 1);
165         format.setInteger("color-format", CodecCapabilities.COLOR_FormatYUV420Flexible);
166         format.setInteger("bitrate-mode", 0);
167         format.setInteger("quality", 1);
168 
169         return format;
170     }
171 
172     // Allocates at most max number of codecs
allocateCodecs(int max)173     protected int allocateCodecs(int max) {
174         boolean shouldSkip = false;
175         MediaCodecInfo info = getCodecInfo(true);
176         if (info != null) {
177             // Try allocating max number of decoders first.
178             String name = info.getName();
179             MediaFormat decoderFormat = createVideoFormat(info);
180             allocateCodecs(max, name, decoderFormat, true);
181 
182             // Try allocating max number of encoder next.
183             info = getCodecInfo(false);
184             if (info != null) {
185                 name = info.getName();
186                 MediaFormat encoderFormat = createVideoEncoderFormat(info);
187                 allocateCodecs(max, name, encoderFormat, false);
188             }
189         } else {
190             shouldSkip = true;
191         }
192 
193         if (shouldSkip) {
194             Log.d(TAG, "test skipped as there's no supported codec.");
195             finishWithResult(ResourceManagerStubActivity.RESULT_CODE_NO_DECODER);
196         }
197 
198         Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
199         return mCodecs.size();
200     }
201 
202 
allocateCodecs(int max, String name, MediaFormat format, boolean decoder)203     protected void allocateCodecs(int max, String name, MediaFormat format, boolean decoder) {
204         MediaCodec codec = null;
205         max += mCodecs.size();
206         int flag = decoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE;
207 
208         for (int i = mCodecs.size(); i < max; ++i) {
209             try {
210                 Log.d(TAG, "Create codec " + name + " #" + i);
211                 codec = MediaCodec.createByCodecName(name);
212                 codec.setCallback(mCallback);
213                 Log.d(TAG, "Configure Codec: " + format);
214                 codec.configure(format, null, null, flag);
215                 Log.d(TAG, "Start codec ");
216                 codec.start();
217                 mCodecs.add(codec);
218                 codec = null;
219             } catch (IllegalArgumentException e) {
220                 Log.d(TAG, "IllegalArgumentException " + e.getMessage());
221                 break;
222             } catch (IOException e) {
223                 Log.d(TAG, "IOException " + e.getMessage());
224                 break;
225             } catch (MediaCodec.CodecException e) {
226                 Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
227                 break;
228             } finally {
229                 if (codec != null) {
230                     Log.d(TAG, "release codec");
231                     codec.release();
232                     codec = null;
233                 }
234             }
235         }
236     }
237 
finishWithResult(int result)238     protected void finishWithResult(int result) {
239         for (int i = 0; i < mCodecs.size(); ++i) {
240             Log.d(TAG, "release codec #" + i);
241             mCodecs.get(i).release();
242         }
243         mCodecs.clear();
244         setResult(result);
245         finish();
246         Log.d(TAG, "Activity finished with: " + result);
247     }
248 
doUseCodecs()249     private boolean doUseCodecs() {
250         int current = 0;
251         try {
252             for (current = 0; current < mCodecs.size(); ++current) {
253                 mCodecs.get(current).getName();
254             }
255         } catch (MediaCodec.CodecException e) {
256             Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
257             if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
258                 Log.d(TAG, "Remove codec " + current + " from the list");
259                 mCodecs.get(current).release();
260                 mCodecs.remove(current);
261                 mGotReclaimedException = true;
262             }
263             return false;
264         }
265         return true;
266     }
267 
useCodecs()268     protected void useCodecs() {
269         mWorkerThread = new Thread(new Runnable() {
270             @Override
271             public void run() {
272                 Log.i(TAG, "Started the thread");
273                 long start = System.currentTimeMillis();
274                 long timeSinceStartedMs = 0;
275                 boolean success = true;
276                 while (success && (timeSinceStartedMs < 15000)) {  // timeout in 15s
277                     success = doUseCodecs();
278                     try {
279                         // wait for 50ms before calling doUseCodecs again.
280                         Thread.sleep(50 /* millis */);
281                     } catch (InterruptedException e) { }
282                     timeSinceStartedMs = System.currentTimeMillis() - start;
283                 }
284                 if (mGotReclaimedException) {
285                     Log.d(TAG, "Got expected reclaim exception.");
286                     // As expected a Codec was reclaimed from this (background) Activity.
287                     // So, finish with success.
288                     finishWithResult(RESULT_OK);
289                 } else if (success) {
290                     Log.d(TAG, "No codec reclaim exception, but codec operations successful.");
291                     // Though we were expecting reclaim event, it could be possible that
292                     // oem was able to allocate another codec (had enough resources) for the
293                     // foreground app. In those case, we need to pass this.
294                     finishWithResult(RESULT_OK);
295                 } else {
296                     Log.d(TAG, "Stopped with an unexpected codec exception.");
297                     // We were expecting reclaim event OR codec operations to be successful.
298                     // In neither of the case, some unexpected error happened.
299                     // So, fail the case.
300                     finishWithResult(RESULT_CANCELED);
301                 }
302             }
303         });
304         mWorkerThread.start();
305     }
306 }
307